From 3b0c5f22bc4bfb623506582223e73a5c3aadcc06 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Thu, 4 Apr 2024 20:20:46 -0600 Subject: [PATCH 01/16] build: first contracts --- .gitmodules | 4 ---- foundry.toml | 8 ++++++-- lib/openzeppelin-contracts | 1 - src/Counter.sol | 14 -------------- src/L1 Escrow.sol | 8 ++++++++ src/L1Deployer.sol | 6 ++++++ src/L2Deployer.sol | 6 ++++++ test/Counter.t.sol | 24 ------------------------ 8 files changed, 26 insertions(+), 45 deletions(-) delete mode 160000 lib/openzeppelin-contracts delete mode 100644 src/Counter.sol create mode 100644 src/L1 Escrow.sol create mode 100644 src/L1Deployer.sol create mode 100644 src/L2Deployer.sol delete mode 100644 test/Counter.t.sol diff --git a/.gitmodules b/.gitmodules index 7448da7..ba58715 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,10 +9,6 @@ path = lib/yearn-vaults-v3 url = https://github.com/yearn/yearn-vaults-v3 ref = v3.0.2 -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts - ref = v4.9.5 [submodule "lib/tokenized-strategy-periphery"] path = lib/tokenized-strategy-periphery url = https://github.com/yearn/tokenized-strategy-periphery diff --git a/foundry.toml b/foundry.toml index ca9052e..6818161 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,12 +7,16 @@ auto_detect_remappings = false remappings = [ 'forge-std/=lib/forge-std/src/', - '@openzeppelin/=lib/openzeppelin-contracts/', + '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', "@tokenized-strategy/=lib/tokenized-strategy/src/", "@yearn-vaults/=lib/yearn-vaults-v3/contracts/", "ds-test/=lib/forge-std/lib/ds-test/src/", "@periphery/=lib/tokenized-strategy-periphery/src/", - "@vault-periphery/=lib/vault-periphery/src" + "@vault-periphery/=lib/vault-periphery/src", + "@zkevm-stb/=lib/zkevm-stb/src/", + "@src/=src/", + "@openzeppelin/contracts-upgradeable/=lib/zkevm-stb/lib/openzeppelin-contracts-upgradeable/contracts/", + #"@openzeppelin/contracts/=lib/zkevm-stb/lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" ] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index bd325d5..0000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd325d56b4c62c9c5c1aff048c37c6bb18ac0290 diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/L1 Escrow.sol b/src/L1 Escrow.sol new file mode 100644 index 0000000..2857a51 --- /dev/null +++ b/src/L1 Escrow.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.23; + +import {L1Escrow} from "@zkevm-stb/L1Escrow.sol"; + +contract L1YearnEscrow is L1Escrow { + +} \ No newline at end of file diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol new file mode 100644 index 0000000..e1f7bfa --- /dev/null +++ b/src/L1Deployer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.18; + +contract L1Deployer { + +} \ No newline at end of file diff --git a/src/L2Deployer.sol b/src/L2Deployer.sol new file mode 100644 index 0000000..49214e5 --- /dev/null +++ b/src/L2Deployer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.18; + +contract L2Deployer { + +} \ No newline at end of file diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index e9b9e6a..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console2} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From 6d5b2e1a175aba46f6f59466663fe546a56199ef Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Thu, 4 Apr 2024 20:21:20 -0600 Subject: [PATCH 02/16] forge install: openzeppelin-contracts v5.0.1 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index ba58715..93272c2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,6 @@ [submodule "lib/zkevm-stb"] path = lib/zkevm-stb url = https://github.com/pyk/zkevm-stb +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..01ef448 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 01ef448981be9d20ca85f2faf6ebdf591ce409f3 From 0d09df84c431ad495e4475649f21906ec4ee98c0 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Sat, 6 Apr 2024 10:58:45 -0600 Subject: [PATCH 03/16] chore: updating --- .gitmodules | 15 +-------------- foundry.toml | 2 ++ lib/tokenized-strategy | 1 - lib/tokenized-strategy-periphery | 1 - lib/vault-periphery | 1 - lib/yearn-vaults-v3 | 1 - package.json | 2 +- src/L1Deployer.sol | 4 ++-- src/L2Deployer.sol | 2 +- test/utils/ExtendedTest.sol | 2 +- test/utils/Setup.sol | 6 +++--- 11 files changed, 11 insertions(+), 26 deletions(-) delete mode 160000 lib/tokenized-strategy delete mode 160000 lib/tokenized-strategy-periphery delete mode 160000 lib/vault-periphery delete mode 160000 lib/yearn-vaults-v3 diff --git a/.gitmodules b/.gitmodules index 93272c2..fbeab48 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,23 +1,10 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/tokenized-strategy"] - path = lib/tokenized-strategy - url = https://github.com/yearn/tokenized-strategy - ref = v3.0.2 -[submodule "lib/yearn-vaults-v3"] - path = lib/yearn-vaults-v3 - url = https://github.com/yearn/yearn-vaults-v3 - ref = v3.0.2 -[submodule "lib/tokenized-strategy-periphery"] - path = lib/tokenized-strategy-periphery - url = https://github.com/yearn/tokenized-strategy-periphery -[submodule "lib/vault-periphery"] - path = lib/vault-periphery - url = https://github.com/yearn/vault-periphery [submodule "lib/zkevm-stb"] path = lib/zkevm-stb url = https://github.com/pyk/zkevm-stb [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts + release = v5.0.1 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 6818161..582f708 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,7 @@ [profile.default] src = "src" +#solc = "0.8.23" +auto_detect_solc = true out = "out" libs = ["lib"] diff --git a/lib/tokenized-strategy b/lib/tokenized-strategy deleted file mode 160000 index 0d90dee..0000000 --- a/lib/tokenized-strategy +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0d90dee170d53a0e04af3ff41d2f7a4f3ac395bd diff --git a/lib/tokenized-strategy-periphery b/lib/tokenized-strategy-periphery deleted file mode 160000 index abdec36..0000000 --- a/lib/tokenized-strategy-periphery +++ /dev/null @@ -1 +0,0 @@ -Subproject commit abdec3622bea14311660507b79f531e2c13ca33a diff --git a/lib/vault-periphery b/lib/vault-periphery deleted file mode 160000 index 8efd722..0000000 --- a/lib/vault-periphery +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8efd722a46aa051b1606e49be0dfe1776f6d81b9 diff --git a/lib/yearn-vaults-v3 b/lib/yearn-vaults-v3 deleted file mode 160000 index cb9be33..0000000 --- a/lib/yearn-vaults-v3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cb9be33989dd0f482462c3ee429dff6900e05dfd diff --git a/package.json b/package.json index 46f5b69..5ea7501 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "prettier": "^2.5.1", "prettier-plugin-solidity": "^1.0.0-beta.19", "pretty-quick": "^3.1.3", - "solc": "0.8.18", + "solc": "0.8.23", "solhint": "^3.3.7", "solhint-plugin-prettier": "^0.0.5" }, diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index e1f7bfa..45307e7 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.18; +pragma solidity ^0.8.20; contract L1Deployer { - + } \ No newline at end of file diff --git a/src/L2Deployer.sol b/src/L2Deployer.sol index 49214e5..e289404 100644 --- a/src/L2Deployer.sol +++ b/src/L2Deployer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.18; +pragma solidity ^0.8.20; contract L2Deployer { diff --git a/test/utils/ExtendedTest.sol b/test/utils/ExtendedTest.sol index e8fcc6c..52f9fa0 100644 --- a/test/utils/ExtendedTest.sol +++ b/test/utils/ExtendedTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.18; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index d8fd48f..c50b5f5 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.18; +pragma solidity ^0.8.20; import "forge-std/console.sol"; import {ExtendedTest} from "./ExtendedTest.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +//import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +//import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol/"; import {Roles} from "@yearn-vaults/interfaces/Roles.sol"; import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; From 6868400154fd7d9de069a58b6ff0351f41e3ef32 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Sat, 6 Apr 2024 11:01:25 -0600 Subject: [PATCH 04/16] forge install: tokenized-strategy-periphery aa404867f4e02afd209e27f2544a6ac0e1f4fb89 --- .gitmodules | 5 ++++- lib/tokenized-strategy-periphery | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 lib/tokenized-strategy-periphery diff --git a/.gitmodules b/.gitmodules index fbeab48..588a0bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,4 +7,7 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts - release = v5.0.1 \ No newline at end of file + release = v5.0.1 +[submodule "lib/tokenized-strategy-periphery"] + path = lib/tokenized-strategy-periphery + url = https://github.com/yearn/tokenized-strategy-periphery diff --git a/lib/tokenized-strategy-periphery b/lib/tokenized-strategy-periphery new file mode 160000 index 0000000..aa40486 --- /dev/null +++ b/lib/tokenized-strategy-periphery @@ -0,0 +1 @@ +Subproject commit aa404867f4e02afd209e27f2544a6ac0e1f4fb89 From 2852b3bee61167ae37772207670162a4c66d3de7 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Sat, 6 Apr 2024 11:01:40 -0600 Subject: [PATCH 05/16] forge install: vault-periphery 19036db4d5f8d0878478584be8eaa42b04766177 --- .gitmodules | 3 +++ lib/vault-periphery | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/vault-periphery diff --git a/.gitmodules b/.gitmodules index 588a0bf..a27d191 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/tokenized-strategy-periphery"] path = lib/tokenized-strategy-periphery url = https://github.com/yearn/tokenized-strategy-periphery +[submodule "lib/vault-periphery"] + path = lib/vault-periphery + url = https://github.com/yearn/vault-periphery diff --git a/lib/vault-periphery b/lib/vault-periphery new file mode 160000 index 0000000..19036db --- /dev/null +++ b/lib/vault-periphery @@ -0,0 +1 @@ +Subproject commit 19036db4d5f8d0878478584be8eaa42b04766177 From c7aed93c5b9031881d436dff4b6378e30533497f Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Sat, 6 Apr 2024 12:10:58 -0600 Subject: [PATCH 06/16] chore: vault periphery --- .gitmodules | 3 - foundry.toml | 12 +- lib/tokenized-strategy-periphery | 1 - src/L1Deployer.sol | 2 + src/{L1 Escrow.sol => L1YearnEscrow.sol} | 2 +- src/interfaces/IBaseStrategy.sol | 30 +++ src/interfaces/IEvents.sol | 113 ++++++++++ src/interfaces/IFactory.sol | 6 + src/interfaces/IStrategy.sol | 7 + src/interfaces/ITokenizedStrategy.sol | 156 ++++++++++++++ src/interfaces/IVault.sol | 249 +++++++++++++++++++++++ src/interfaces/IVaultFactory.sol | 75 +++++++ src/interfaces/Roles.sol | 21 ++ src/interfaces/VaultConstants.sol | 11 + test/mocks/MockStrategy.sol | 10 + test/utils/Setup.sol | 35 ++-- 16 files changed, 706 insertions(+), 27 deletions(-) delete mode 160000 lib/tokenized-strategy-periphery rename src/{L1 Escrow.sol => L1YearnEscrow.sol} (84%) create mode 100644 src/interfaces/IBaseStrategy.sol create mode 100644 src/interfaces/IEvents.sol create mode 100644 src/interfaces/IFactory.sol create mode 100644 src/interfaces/IStrategy.sol create mode 100644 src/interfaces/ITokenizedStrategy.sol create mode 100644 src/interfaces/IVault.sol create mode 100644 src/interfaces/IVaultFactory.sol create mode 100644 src/interfaces/Roles.sol create mode 100644 src/interfaces/VaultConstants.sol create mode 100644 test/mocks/MockStrategy.sol diff --git a/.gitmodules b/.gitmodules index a27d191..c2353c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,9 +8,6 @@ path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts release = v5.0.1 -[submodule "lib/tokenized-strategy-periphery"] - path = lib/tokenized-strategy-periphery - url = https://github.com/yearn/tokenized-strategy-periphery [submodule "lib/vault-periphery"] path = lib/vault-periphery url = https://github.com/yearn/vault-periphery diff --git a/foundry.toml b/foundry.toml index 582f708..b030f8e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,24 +1,22 @@ [profile.default] src = "src" -#solc = "0.8.23" -auto_detect_solc = true out = "out" libs = ["lib"] - +solc = "0.8.23" +evm_version = "shanghai" auto_detect_remappings = false remappings = [ 'forge-std/=lib/forge-std/src/', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', - "@tokenized-strategy/=lib/tokenized-strategy/src/", - "@yearn-vaults/=lib/yearn-vaults-v3/contracts/", + #"@tokenized-strategy/=lib/tokenized-strategy/src/", + #"@yearn-vaults/=lib/yearn-vaults-v3/contracts/", "ds-test/=lib/forge-std/lib/ds-test/src/", "@periphery/=lib/tokenized-strategy-periphery/src/", - "@vault-periphery/=lib/vault-periphery/src", + "@vault-periphery/=lib/vault-periphery/contracts/", "@zkevm-stb/=lib/zkevm-stb/src/", "@src/=src/", "@openzeppelin/contracts-upgradeable/=lib/zkevm-stb/lib/openzeppelin-contracts-upgradeable/contracts/", - #"@openzeppelin/contracts/=lib/zkevm-stb/lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" ] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/tokenized-strategy-periphery b/lib/tokenized-strategy-periphery deleted file mode 160000 index aa40486..0000000 --- a/lib/tokenized-strategy-periphery +++ /dev/null @@ -1 +0,0 @@ -Subproject commit aa404867f4e02afd209e27f2544a6ac0e1f4fb89 diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index 45307e7..7ff0c40 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; +import {L1YearnEscrow} from "./L1YearnEscrow.sol"; + contract L1Deployer { } \ No newline at end of file diff --git a/src/L1 Escrow.sol b/src/L1YearnEscrow.sol similarity index 84% rename from src/L1 Escrow.sol rename to src/L1YearnEscrow.sol index 2857a51..4fbbcad 100644 --- a/src/L1 Escrow.sol +++ b/src/L1YearnEscrow.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.23; +pragma solidity ^0.8.20; import {L1Escrow} from "@zkevm-stb/L1Escrow.sol"; diff --git a/src/interfaces/IBaseStrategy.sol b/src/interfaces/IBaseStrategy.sol new file mode 100644 index 0000000..a76dd50 --- /dev/null +++ b/src/interfaces/IBaseStrategy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +interface IBaseStrategy { + function tokenizedStrategyAddress() external view returns (address); + + /*////////////////////////////////////////////////////////////// + IMMUTABLE FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function availableDepositLimit( + address _owner + ) external view returns (uint256); + + function availableWithdrawLimit( + address _owner + ) external view returns (uint256); + + function deployFunds(uint256 _assets) external; + + function freeFunds(uint256 _amount) external; + + function harvestAndReport() external returns (uint256); + + function tendThis(uint256 _totalIdle) external; + + function shutdownWithdraw(uint256 _amount) external; + + function tendTrigger() external view returns (bool, bytes memory); +} diff --git a/src/interfaces/IEvents.sol b/src/interfaces/IEvents.sol new file mode 100644 index 0000000..c4046d9 --- /dev/null +++ b/src/interfaces/IEvents.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +interface IEvents { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emitted when a strategy is shutdown. + */ + event StrategyShutdown(); + + /** + * @notice Emitted on the initialization of any new `strategy` that uses `asset` + * with this specific `apiVersion`. + */ + event NewTokenizedStrategy( + address indexed strategy, + address indexed asset, + string apiVersion + ); + + /** + * @notice Emitted when the strategy reports `profit` or `loss` and + * `performanceFees` and `protocolFees` are paid out. + */ + event Reported( + uint256 profit, + uint256 loss, + uint256 protocolFees, + uint256 performanceFees + ); + + /** + * @notice Emitted when the 'performanceFeeRecipient' address is + * updated to 'newPerformanceFeeRecipient'. + */ + event UpdatePerformanceFeeRecipient( + address indexed newPerformanceFeeRecipient + ); + + /** + * @notice Emitted when the 'keeper' address is updated to 'newKeeper'. + */ + event UpdateKeeper(address indexed newKeeper); + + /** + * @notice Emitted when the 'performanceFee' is updated to 'newPerformanceFee'. + */ + event UpdatePerformanceFee(uint16 newPerformanceFee); + + /** + * @notice Emitted when the 'management' address is updated to 'newManagement'. + */ + event UpdateManagement(address indexed newManagement); + + /** + * @notice Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'. + */ + event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); + + /** + * @notice Emitted when the 'profitMaxUnlockTime' is updated to 'newProfitMaxUnlockTime'. + */ + event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); + + /** + * @notice Emitted when the 'pendingManagement' address is updated to 'newPendingManagement'. + */ + event UpdatePendingManagement(address indexed newPendingManagement); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the `caller` has exchanged `assets` for `shares`, + * and transferred those `shares` to `owner`. + */ + event Deposit( + address indexed caller, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Emitted when the `caller` has exchanged `owner`s `shares` for `assets`, + * and transferred those `assets` to `receiver`. + */ + event Withdraw( + address indexed caller, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); +} diff --git a/src/interfaces/IFactory.sol b/src/interfaces/IFactory.sol new file mode 100644 index 0000000..57a3930 --- /dev/null +++ b/src/interfaces/IFactory.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +interface IFactory { + function protocol_fee_config() external view returns (uint16, address); +} diff --git a/src/interfaces/IStrategy.sol b/src/interfaces/IStrategy.sol new file mode 100644 index 0000000..7c5a59a --- /dev/null +++ b/src/interfaces/IStrategy.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {ITokenizedStrategy} from "./ITokenizedStrategy.sol"; +import {IBaseStrategy} from "./IBaseStrategy.sol"; + +interface IStrategy is IBaseStrategy, ITokenizedStrategy {} diff --git a/src/interfaces/ITokenizedStrategy.sol b/src/interfaces/ITokenizedStrategy.sol new file mode 100644 index 0000000..8e10d22 --- /dev/null +++ b/src/interfaces/ITokenizedStrategy.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; + +// Interface that implements the 4626 standard and the implementation functions +interface ITokenizedStrategy is IERC4626, IERC20Permit { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event StrategyShutdown(); + + event NewTokenizedStrategy( + address indexed strategy, + address indexed asset, + string apiVersion + ); + + event Reported( + uint256 profit, + uint256 loss, + uint256 protocolFees, + uint256 performanceFees + ); + + event UpdatePerformanceFeeRecipient( + address indexed newPerformanceFeeRecipient + ); + + event UpdateKeeper(address indexed newKeeper); + + event UpdatePerformanceFee(uint16 newPerformanceFee); + + event UpdateManagement(address indexed newManagement); + + event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); + + event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); + + event UpdatePendingManagement(address indexed newPendingManagement); + + /*////////////////////////////////////////////////////////////// + INITIALIZATION + //////////////////////////////////////////////////////////////*/ + + function initialize( + address _asset, + string memory _name, + address _management, + address _performanceFeeRecipient, + address _keeper + ) external; + + /*////////////////////////////////////////////////////////////// + NON-STANDARD 4626 OPTIONS + //////////////////////////////////////////////////////////////*/ + + function withdraw( + uint256 assets, + address receiver, + address owner, + uint256 maxLoss + ) external returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner, + uint256 maxLoss + ) external returns (uint256); + + /*////////////////////////////////////////////////////////////// + MODIFIER HELPERS + //////////////////////////////////////////////////////////////*/ + + function requireManagement(address _sender) external view; + + function requireKeeperOrManagement(address _sender) external view; + + function requireEmergencyAuthorized(address _sender) external view; + + /*////////////////////////////////////////////////////////////// + KEEPERS FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function tend() external; + + function report() external returns (uint256 _profit, uint256 _loss); + + /*////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////*/ + + function MAX_FEE() external view returns (uint16); + + function FACTORY() external view returns (address); + + /*////////////////////////////////////////////////////////////// + GETTERS + //////////////////////////////////////////////////////////////*/ + + function apiVersion() external view returns (string memory); + + function pricePerShare() external view returns (uint256); + + function management() external view returns (address); + + function pendingManagement() external view returns (address); + + function keeper() external view returns (address); + + function emergencyAdmin() external view returns (address); + + function performanceFee() external view returns (uint16); + + function performanceFeeRecipient() external view returns (address); + + function fullProfitUnlockDate() external view returns (uint256); + + function profitUnlockingRate() external view returns (uint256); + + function profitMaxUnlockTime() external view returns (uint256); + + function lastReport() external view returns (uint256); + + function isShutdown() external view returns (bool); + + function unlockedShares() external view returns (uint256); + + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + + function setPendingManagement(address) external; + + function acceptManagement() external; + + function setKeeper(address _keeper) external; + + function setEmergencyAdmin(address _emergencyAdmin) external; + + function setPerformanceFee(uint16 _performanceFee) external; + + function setPerformanceFeeRecipient( + address _performanceFeeRecipient + ) external; + + function setProfitMaxUnlockTime(uint256 _profitMaxUnlockTime) external; + + function shutdownStrategy() external; + + function emergencyWithdraw(uint256 _amount) external; +} diff --git a/src/interfaces/IVault.sol b/src/interfaces/IVault.sol new file mode 100644 index 0000000..27e6b7c --- /dev/null +++ b/src/interfaces/IVault.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.18; + +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; + +interface IVault is IERC4626 { + // STRATEGY EVENTS + event StrategyChanged(address indexed strategy, uint256 change_type); + event StrategyReported( + address indexed strategy, + uint256 gain, + uint256 loss, + uint256 current_debt, + uint256 protocol_fees, + uint256 total_fees, + uint256 total_refunds + ); + // DEBT MANAGEMENT EVENTS + event DebtUpdated( + address indexed strategy, + uint256 current_debt, + uint256 new_debt + ); + // ROLE UPDATES + event RoleSet(address indexed account, uint256 role); + event UpdateRoleManager(address indexed role_manager); + + event UpdateAccountant(address indexed accountant); + event UpdateDefaultQueue(address[] new_default_queue); + event UpdateUseDefaultQueue(bool use_default_queue); + event UpdatedMaxDebtForStrategy( + address indexed sender, + address indexed strategy, + uint256 new_debt + ); + event UpdateDepositLimit(uint256 deposit_limit); + event UpdateMinimumTotalIdle(uint256 minimum_total_idle); + event UpdateProfitMaxUnlockTime(uint256 profit_max_unlock_time); + event DebtPurchased(address indexed strategy, uint256 amount); + event Shutdown(); + + struct StrategyParams { + uint256 activation; + uint256 last_report; + uint256 current_debt; + uint256 max_debt; + } + + function FACTORY() external view returns (uint256); + + function strategies(address) external view returns (StrategyParams memory); + + function default_queue(uint256) external view returns (address); + + function use_default_queue() external view returns (bool); + + function minimum_total_idle() external view returns (uint256); + + function deposit_limit() external view returns (uint256); + + function deposit_limit_module() external view returns (address); + + function withdraw_limit_module() external view returns (address); + + function accountant() external view returns (address); + + function roles(address) external view returns (uint256); + + function role_manager() external view returns (address); + + function future_role_manager() external view returns (address); + + function isShutdown() external view returns (bool); + + function nonces(address) external view returns (uint256); + + function initialize( + address, + string memory, + string memory, + address, + uint256 + ) external; + + function set_accountant(address new_accountant) external; + + function set_default_queue(address[] memory new_default_queue) external; + + function set_use_default_queue(bool) external; + + function set_deposit_limit(uint256 deposit_limit) external; + + function set_deposit_limit( + uint256 deposit_limit, + bool should_override + ) external; + + function set_deposit_limit_module( + address new_deposit_limit_module + ) external; + + function set_deposit_limit_module( + address new_deposit_limit_module, + bool should_override + ) external; + + function set_withdraw_limit_module( + address new_withdraw_limit_module + ) external; + + function set_minimum_total_idle(uint256 minimum_total_idle) external; + + function setProfitMaxUnlockTime( + uint256 new_profit_max_unlock_time + ) external; + + function set_role(address account, uint256 role) external; + + function add_role(address account, uint256 role) external; + + function remove_role(address account, uint256 role) external; + + function transfer_role_manager(address role_manager) external; + + function accept_role_manager() external; + + function unlockedShares() external view returns (uint256); + + function pricePerShare() external view returns (uint256); + + function get_default_queue() external view returns (address[] memory); + + function process_report( + address strategy + ) external returns (uint256, uint256); + + function buy_debt(address strategy, uint256 amount) external; + + function add_strategy(address new_strategy) external; + + function revoke_strategy(address strategy) external; + + function force_revoke_strategy(address strategy) external; + + function update_max_debt_for_strategy( + address strategy, + uint256 new_max_debt + ) external; + + function update_debt( + address strategy, + uint256 target_debt + ) external returns (uint256); + + function update_debt( + address strategy, + uint256 target_debt, + uint256 max_loss + ) external returns (uint256); + + function shutdown_vault() external; + + function totalIdle() external view returns (uint256); + + function totalDebt() external view returns (uint256); + + function apiVersion() external view returns (string memory); + + function assess_share_of_unrealised_losses( + address strategy, + uint256 assets_needed + ) external view returns (uint256); + + function profitMaxUnlockTime() external view returns (uint256); + + function fullProfitUnlockDate() external view returns (uint256); + + function profitUnlockingRate() external view returns (uint256); + + function lastProfitUpdate() external view returns (uint256); + + //// NON-STANDARD ERC-4626 FUNCTIONS \\\\ + + function withdraw( + uint256 assets, + address receiver, + address owner, + uint256 max_loss + ) external returns (uint256); + + function withdraw( + uint256 assets, + address receiver, + address owner, + uint256 max_loss, + address[] memory strategies + ) external returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner, + uint256 max_loss + ) external returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner, + uint256 max_loss, + address[] memory strategies + ) external returns (uint256); + + function maxWithdraw( + address owner, + uint256 max_loss + ) external view returns (uint256); + + function maxWithdraw( + address owner, + uint256 max_loss, + address[] memory strategies + ) external view returns (uint256); + + function maxRedeem( + address owner, + uint256 max_loss + ) external view returns (uint256); + + function maxRedeem( + address owner, + uint256 max_loss, + address[] memory strategies + ) external view returns (uint256); + + //// NON-STANDARD ERC-20 FUNCTIONS \\\\ + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function permit( + address owner, + address spender, + uint256 amount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (bool); +} diff --git a/src/interfaces/IVaultFactory.sol b/src/interfaces/IVaultFactory.sol new file mode 100644 index 0000000..56ea402 --- /dev/null +++ b/src/interfaces/IVaultFactory.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.18; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +interface IVaultFactory { + event NewVault(address indexed vaultAddress, address indexed asset); + event UpdateProtocolFeeBps( + uint16 oldProtocolFeeBps, + uint16 newProtocolFeeBps + ); + event UpdateProtocolFeeRecipient( + address oldProtocolFeeRecipient, + address newProtocolFeeRecipient + ); + event UpdateCustomProtocolFee(address vault, uint16 newCustomProtocolFee); + event RemovedCustomProtocolFee(address vault); + event FactoryShutdown(); + event NewPendingGovernance(address newPendingGovernance); + event UpdateGovernance(address newGovernance); + + function shutdown() external view returns (bool); + + function governance() external view returns (address); + + function pending_governance() external view returns (address); + + function name() external view returns (string memory); + + function default_protocol_fee_config() external view returns (uint256); + + function custom_protocol_fee(address) external view returns (uint16); + + function use_custom_protocol_fee(address) external view returns (bool); + + function deploy_new_vault( + address asset, + string memory name, + string memory symbol, + address role_manager, + uint256 profit_max_unlock_time + ) external returns (address); + + function vault_original() external view returns (address); + + function apiVersion() external view returns (string memory); + + function protocol_fee_config() + external + view + returns (uint16 fee_bps, address fee_recipient); + + function protocol_fee_config( + address vault + ) external view returns (uint16 fee_bps, address fee_recipient); + + function set_protocol_fee_bps(uint16 new_protocol_fee_bps) external; + + function set_protocol_fee_recipient( + address new_protocol_fee_recipient + ) external; + + function set_custom_protocol_fee_bps( + address vault, + uint16 new_custom_protocol_fee + ) external; + + function remove_custom_protocol_fee(address vault) external; + + function shutdown_factory() external; + + function set_governance(address new_governance) external; + + function accept_governance() external; +} diff --git a/src/interfaces/Roles.sol b/src/interfaces/Roles.sol new file mode 100644 index 0000000..6715836 --- /dev/null +++ b/src/interfaces/Roles.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.18; + +// prettier-ignore +library Roles { + uint256 internal constant ADD_STRATEGY_MANAGER = 1; + uint256 internal constant REVOKE_STRATEGY_MANAGER = 2; + uint256 internal constant FORCE_REVOKE_MANAGER = 4; + uint256 internal constant ACCOUNTANT_MANAGER = 8; + uint256 internal constant QUEUE_MANAGER = 16; + uint256 internal constant REPORTING_MANAGER = 32; + uint256 internal constant DEBT_MANAGER = 64; + uint256 internal constant MAX_DEBT_MANAGER = 128; + uint256 internal constant DEPOSIT_LIMIT_MANAGER = 256; + uint256 internal constant WITHDRAW_LIMIT_MANAGER = 512; + uint256 internal constant MINIMUM_IDLE_MANAGER = 1024; + uint256 internal constant PROFIT_UNLOCK_MANAGER = 2048; + uint256 internal constant DEBT_PURCHASER = 4096; + uint256 internal constant EMERGENCY_MANAGER = 8192; + uint256 internal constant ALL = 16383; +} diff --git a/src/interfaces/VaultConstants.sol b/src/interfaces/VaultConstants.sol new file mode 100644 index 0000000..de5b168 --- /dev/null +++ b/src/interfaces/VaultConstants.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.18; + +// prettier-ignore +contract VaultConstants { + uint256 public constant MAX_QUEUE = 10; + uint256 public constant MAX_BPS = 10_000; + uint256 public constant MAX_BPS_EXTENDED = 1_000_000_000_000; + uint256 public constant STRATEGY_ADDED = 1; + uint256 public constant STRATEGY_REVOKED = 2; +} diff --git a/test/mocks/MockStrategy.sol b/test/mocks/MockStrategy.sol new file mode 100644 index 0000000..30390c1 --- /dev/null +++ b/test/mocks/MockStrategy.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {ERC4626Mock} from "@openzeppelin/contracts/mocks/token/ERC4626Mock.sol"; + +contract MockStrategy is ERC4626Mock { + + constructor(address _asset) ERC4626Mock(_asset) { + } +} \ No newline at end of file diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index c50b5f5..ee7691f 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.20; +pragma solidity >=0.8.18; import "forge-std/console.sol"; import {ExtendedTest} from "./ExtendedTest.sol"; -//import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; //import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol/"; -import {Roles} from "@yearn-vaults/interfaces/Roles.sol"; -import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; -import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol"; -import {IVaultFactory} from "@yearn-vaults/interfaces/IVaultFactory.sol"; +import {Roles} from "../../src/interfaces/Roles.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {IStrategy} from "../../src/interfaces/IStrategy.sol"; +import {IVaultFactory} from "../../src/interfaces/IVaultFactory.sol"; -import {MockStrategy} from "@periphery/test/mocks/MockStrategy.sol"; -import {Clonable} from "@periphery/utils/Clonable.sol"; +import {Registry, RegistryFactory} from "@vault-periphery/registry/RegistryFactory.sol"; -contract Setup is ExtendedTest, Clonable { - using SafeERC20 for ERC20; +import {MockStrategy} from "../mocks/MockStrategy.sol"; + +contract Setup is ExtendedTest { // Contract instances that we will use repeatedly. ERC20 public asset; @@ -24,7 +24,8 @@ contract Setup is ExtendedTest, Clonable { // Vault contracts to test with. IVault public vault; - IVaultFactory public vaultFactory; + // Vault Factory v3.0.2 + IVaultFactory public vaultFactory = IVaultFactory(0x444045c5C13C246e117eD36437303cac8E250aB0); // Addresses for different roles we will use repeatedly. address public user = address(10); @@ -52,14 +53,14 @@ contract Setup is ExtendedTest, Clonable { _setTokenAddrs(); // Make sure everything works with USDT - asset = ERC20(tokenAddrs["USDT"]); + asset = ERC20(tokenAddrs["DAI"]); // Set decimals decimals = asset.decimals(); - mockStrategy = setUpStrategy(); + vault = setupVault(); - vaultFactory = IVaultFactory(mockStrategy.FACTORY()); + mockStrategy = setUpStrategy(); // label all the used addresses for traces vm.label(daddy, "daddy"); @@ -72,6 +73,10 @@ contract Setup is ExtendedTest, Clonable { vm.label(performanceFeeRecipient, "performanceFeeRecipient"); } + function setupVault() public returns (IVault) { + + } + function setUpStrategy() public returns (IStrategy) { // we save the strategy as a IStrategyInterface to give it the needed interface IStrategy _strategy = IStrategy( @@ -97,7 +102,7 @@ contract Setup is ExtendedTest, Clonable { uint256 _amount ) public { vm.startPrank(_user); - asset.safeApprove(address(_strategy), _amount); + asset.approve(address(_strategy), _amount); _strategy.deposit(_amount, _user); vm.stopPrank(); From 0eed5b031a77b268498d3d8055c1b0c39d352a8e Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Sat, 6 Apr 2024 12:11:04 -0600 Subject: [PATCH 07/16] forge install: tokenized-strategy-periphery aa404867f4e02afd209e27f2544a6ac0e1f4fb89 --- .gitmodules | 3 +++ lib/tokenized-strategy-periphery | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/tokenized-strategy-periphery diff --git a/.gitmodules b/.gitmodules index c2353c9..8c9ffc0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/vault-periphery"] path = lib/vault-periphery url = https://github.com/yearn/vault-periphery +[submodule "lib/tokenized-strategy-periphery"] + path = lib/tokenized-strategy-periphery + url = https://github.com/yearn/tokenized-strategy-periphery diff --git a/lib/tokenized-strategy-periphery b/lib/tokenized-strategy-periphery new file mode 160000 index 0000000..aa40486 --- /dev/null +++ b/lib/tokenized-strategy-periphery @@ -0,0 +1 @@ +Subproject commit aa404867f4e02afd209e27f2544a6ac0e1f4fb89 From 8e7c975f949637909977309d0182215925a5da07 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 8 Apr 2024 08:32:16 -0600 Subject: [PATCH 08/16] forge install: yearn-vaults-v3 9fbc614bbce9d7cbad42e284a15f0f43cf1a673f --- .gitmodules | 3 +++ lib/yearn-vaults-v3 | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/yearn-vaults-v3 diff --git a/.gitmodules b/.gitmodules index 8c9ffc0..494f6dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "lib/tokenized-strategy-periphery"] path = lib/tokenized-strategy-periphery url = https://github.com/yearn/tokenized-strategy-periphery +[submodule "lib/yearn-vaults-v3"] + path = lib/yearn-vaults-v3 + url = https://github.com/yearn/yearn-vaults-v3 diff --git a/lib/yearn-vaults-v3 b/lib/yearn-vaults-v3 new file mode 160000 index 0000000..9fbc614 --- /dev/null +++ b/lib/yearn-vaults-v3 @@ -0,0 +1 @@ +Subproject commit 9fbc614bbce9d7cbad42e284a15f0f43cf1a673f From 0e8dde3d56a2a5f78bef579c4456790cffde6ddd Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 8 Apr 2024 13:11:10 -0600 Subject: [PATCH 09/16] fix: remove interfaces --- foundry.toml | 4 +- src/L1Deployer.sol | 4 +- src/L1YearnEscrow.sol | 4 +- src/L2Deployer.sol | 4 +- src/interfaces/IVault.sol | 249 ------------------------------ src/interfaces/IVaultFactory.sol | 75 --------- src/interfaces/Roles.sol | 21 --- src/interfaces/VaultConstants.sol | 11 -- test/utils/Setup.sol | 8 +- 9 files changed, 9 insertions(+), 371 deletions(-) delete mode 100644 src/interfaces/IVault.sol delete mode 100644 src/interfaces/IVaultFactory.sol delete mode 100644 src/interfaces/Roles.sol delete mode 100644 src/interfaces/VaultConstants.sol diff --git a/foundry.toml b/foundry.toml index b030f8e..cb6f8cd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,8 +9,8 @@ auto_detect_remappings = false remappings = [ 'forge-std/=lib/forge-std/src/', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', - #"@tokenized-strategy/=lib/tokenized-strategy/src/", - #"@yearn-vaults/=lib/yearn-vaults-v3/contracts/", + "@tokenized-strategy/=lib/tokenized-strategy/src/", + "@yearn-vaults/=lib/yearn-vaults-v3/contracts/", "ds-test/=lib/forge-std/lib/ds-test/src/", "@periphery/=lib/tokenized-strategy-periphery/src/", "@vault-periphery/=lib/vault-periphery/contracts/", diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index 7ff0c40..eedd576 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -3,6 +3,4 @@ pragma solidity ^0.8.20; import {L1YearnEscrow} from "./L1YearnEscrow.sol"; -contract L1Deployer { - -} \ No newline at end of file +contract L1Deployer {} diff --git a/src/L1YearnEscrow.sol b/src/L1YearnEscrow.sol index 4fbbcad..13b9f1e 100644 --- a/src/L1YearnEscrow.sol +++ b/src/L1YearnEscrow.sol @@ -3,6 +3,4 @@ pragma solidity ^0.8.20; import {L1Escrow} from "@zkevm-stb/L1Escrow.sol"; -contract L1YearnEscrow is L1Escrow { - -} \ No newline at end of file +contract L1YearnEscrow is L1Escrow {} diff --git a/src/L2Deployer.sol b/src/L2Deployer.sol index e289404..adf8d51 100644 --- a/src/L2Deployer.sol +++ b/src/L2Deployer.sol @@ -1,6 +1,4 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; -contract L2Deployer { - -} \ No newline at end of file +contract L2Deployer {} diff --git a/src/interfaces/IVault.sol b/src/interfaces/IVault.sol deleted file mode 100644 index 27e6b7c..0000000 --- a/src/interfaces/IVault.sol +++ /dev/null @@ -1,249 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.18; - -import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; - -interface IVault is IERC4626 { - // STRATEGY EVENTS - event StrategyChanged(address indexed strategy, uint256 change_type); - event StrategyReported( - address indexed strategy, - uint256 gain, - uint256 loss, - uint256 current_debt, - uint256 protocol_fees, - uint256 total_fees, - uint256 total_refunds - ); - // DEBT MANAGEMENT EVENTS - event DebtUpdated( - address indexed strategy, - uint256 current_debt, - uint256 new_debt - ); - // ROLE UPDATES - event RoleSet(address indexed account, uint256 role); - event UpdateRoleManager(address indexed role_manager); - - event UpdateAccountant(address indexed accountant); - event UpdateDefaultQueue(address[] new_default_queue); - event UpdateUseDefaultQueue(bool use_default_queue); - event UpdatedMaxDebtForStrategy( - address indexed sender, - address indexed strategy, - uint256 new_debt - ); - event UpdateDepositLimit(uint256 deposit_limit); - event UpdateMinimumTotalIdle(uint256 minimum_total_idle); - event UpdateProfitMaxUnlockTime(uint256 profit_max_unlock_time); - event DebtPurchased(address indexed strategy, uint256 amount); - event Shutdown(); - - struct StrategyParams { - uint256 activation; - uint256 last_report; - uint256 current_debt; - uint256 max_debt; - } - - function FACTORY() external view returns (uint256); - - function strategies(address) external view returns (StrategyParams memory); - - function default_queue(uint256) external view returns (address); - - function use_default_queue() external view returns (bool); - - function minimum_total_idle() external view returns (uint256); - - function deposit_limit() external view returns (uint256); - - function deposit_limit_module() external view returns (address); - - function withdraw_limit_module() external view returns (address); - - function accountant() external view returns (address); - - function roles(address) external view returns (uint256); - - function role_manager() external view returns (address); - - function future_role_manager() external view returns (address); - - function isShutdown() external view returns (bool); - - function nonces(address) external view returns (uint256); - - function initialize( - address, - string memory, - string memory, - address, - uint256 - ) external; - - function set_accountant(address new_accountant) external; - - function set_default_queue(address[] memory new_default_queue) external; - - function set_use_default_queue(bool) external; - - function set_deposit_limit(uint256 deposit_limit) external; - - function set_deposit_limit( - uint256 deposit_limit, - bool should_override - ) external; - - function set_deposit_limit_module( - address new_deposit_limit_module - ) external; - - function set_deposit_limit_module( - address new_deposit_limit_module, - bool should_override - ) external; - - function set_withdraw_limit_module( - address new_withdraw_limit_module - ) external; - - function set_minimum_total_idle(uint256 minimum_total_idle) external; - - function setProfitMaxUnlockTime( - uint256 new_profit_max_unlock_time - ) external; - - function set_role(address account, uint256 role) external; - - function add_role(address account, uint256 role) external; - - function remove_role(address account, uint256 role) external; - - function transfer_role_manager(address role_manager) external; - - function accept_role_manager() external; - - function unlockedShares() external view returns (uint256); - - function pricePerShare() external view returns (uint256); - - function get_default_queue() external view returns (address[] memory); - - function process_report( - address strategy - ) external returns (uint256, uint256); - - function buy_debt(address strategy, uint256 amount) external; - - function add_strategy(address new_strategy) external; - - function revoke_strategy(address strategy) external; - - function force_revoke_strategy(address strategy) external; - - function update_max_debt_for_strategy( - address strategy, - uint256 new_max_debt - ) external; - - function update_debt( - address strategy, - uint256 target_debt - ) external returns (uint256); - - function update_debt( - address strategy, - uint256 target_debt, - uint256 max_loss - ) external returns (uint256); - - function shutdown_vault() external; - - function totalIdle() external view returns (uint256); - - function totalDebt() external view returns (uint256); - - function apiVersion() external view returns (string memory); - - function assess_share_of_unrealised_losses( - address strategy, - uint256 assets_needed - ) external view returns (uint256); - - function profitMaxUnlockTime() external view returns (uint256); - - function fullProfitUnlockDate() external view returns (uint256); - - function profitUnlockingRate() external view returns (uint256); - - function lastProfitUpdate() external view returns (uint256); - - //// NON-STANDARD ERC-4626 FUNCTIONS \\\\ - - function withdraw( - uint256 assets, - address receiver, - address owner, - uint256 max_loss - ) external returns (uint256); - - function withdraw( - uint256 assets, - address receiver, - address owner, - uint256 max_loss, - address[] memory strategies - ) external returns (uint256); - - function redeem( - uint256 shares, - address receiver, - address owner, - uint256 max_loss - ) external returns (uint256); - - function redeem( - uint256 shares, - address receiver, - address owner, - uint256 max_loss, - address[] memory strategies - ) external returns (uint256); - - function maxWithdraw( - address owner, - uint256 max_loss - ) external view returns (uint256); - - function maxWithdraw( - address owner, - uint256 max_loss, - address[] memory strategies - ) external view returns (uint256); - - function maxRedeem( - address owner, - uint256 max_loss - ) external view returns (uint256); - - function maxRedeem( - address owner, - uint256 max_loss, - address[] memory strategies - ) external view returns (uint256); - - //// NON-STANDARD ERC-20 FUNCTIONS \\\\ - - function DOMAIN_SEPARATOR() external view returns (bytes32); - - function permit( - address owner, - address spender, - uint256 amount, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external returns (bool); -} diff --git a/src/interfaces/IVaultFactory.sol b/src/interfaces/IVaultFactory.sol deleted file mode 100644 index 56ea402..0000000 --- a/src/interfaces/IVaultFactory.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.18; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -interface IVaultFactory { - event NewVault(address indexed vaultAddress, address indexed asset); - event UpdateProtocolFeeBps( - uint16 oldProtocolFeeBps, - uint16 newProtocolFeeBps - ); - event UpdateProtocolFeeRecipient( - address oldProtocolFeeRecipient, - address newProtocolFeeRecipient - ); - event UpdateCustomProtocolFee(address vault, uint16 newCustomProtocolFee); - event RemovedCustomProtocolFee(address vault); - event FactoryShutdown(); - event NewPendingGovernance(address newPendingGovernance); - event UpdateGovernance(address newGovernance); - - function shutdown() external view returns (bool); - - function governance() external view returns (address); - - function pending_governance() external view returns (address); - - function name() external view returns (string memory); - - function default_protocol_fee_config() external view returns (uint256); - - function custom_protocol_fee(address) external view returns (uint16); - - function use_custom_protocol_fee(address) external view returns (bool); - - function deploy_new_vault( - address asset, - string memory name, - string memory symbol, - address role_manager, - uint256 profit_max_unlock_time - ) external returns (address); - - function vault_original() external view returns (address); - - function apiVersion() external view returns (string memory); - - function protocol_fee_config() - external - view - returns (uint16 fee_bps, address fee_recipient); - - function protocol_fee_config( - address vault - ) external view returns (uint16 fee_bps, address fee_recipient); - - function set_protocol_fee_bps(uint16 new_protocol_fee_bps) external; - - function set_protocol_fee_recipient( - address new_protocol_fee_recipient - ) external; - - function set_custom_protocol_fee_bps( - address vault, - uint16 new_custom_protocol_fee - ) external; - - function remove_custom_protocol_fee(address vault) external; - - function shutdown_factory() external; - - function set_governance(address new_governance) external; - - function accept_governance() external; -} diff --git a/src/interfaces/Roles.sol b/src/interfaces/Roles.sol deleted file mode 100644 index 6715836..0000000 --- a/src/interfaces/Roles.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.18; - -// prettier-ignore -library Roles { - uint256 internal constant ADD_STRATEGY_MANAGER = 1; - uint256 internal constant REVOKE_STRATEGY_MANAGER = 2; - uint256 internal constant FORCE_REVOKE_MANAGER = 4; - uint256 internal constant ACCOUNTANT_MANAGER = 8; - uint256 internal constant QUEUE_MANAGER = 16; - uint256 internal constant REPORTING_MANAGER = 32; - uint256 internal constant DEBT_MANAGER = 64; - uint256 internal constant MAX_DEBT_MANAGER = 128; - uint256 internal constant DEPOSIT_LIMIT_MANAGER = 256; - uint256 internal constant WITHDRAW_LIMIT_MANAGER = 512; - uint256 internal constant MINIMUM_IDLE_MANAGER = 1024; - uint256 internal constant PROFIT_UNLOCK_MANAGER = 2048; - uint256 internal constant DEBT_PURCHASER = 4096; - uint256 internal constant EMERGENCY_MANAGER = 8192; - uint256 internal constant ALL = 16383; -} diff --git a/src/interfaces/VaultConstants.sol b/src/interfaces/VaultConstants.sol deleted file mode 100644 index de5b168..0000000 --- a/src/interfaces/VaultConstants.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.8.18; - -// prettier-ignore -contract VaultConstants { - uint256 public constant MAX_QUEUE = 10; - uint256 public constant MAX_BPS = 10_000; - uint256 public constant MAX_BPS_EXTENDED = 1_000_000_000_000; - uint256 public constant STRATEGY_ADDED = 1; - uint256 public constant STRATEGY_REVOKED = 2; -} diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index ee7691f..7e54a11 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -7,10 +7,10 @@ import {ExtendedTest} from "./ExtendedTest.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; //import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol/"; -import {Roles} from "../../src/interfaces/Roles.sol"; -import {IVault} from "../../src/interfaces/IVault.sol"; +import {Roles} from "@yearn-vaults/interfaces/Roles.sol"; +import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; import {IStrategy} from "../../src/interfaces/IStrategy.sol"; -import {IVaultFactory} from "../../src/interfaces/IVaultFactory.sol"; +import {IVaultFactory} from "@yearn-vaults/interfaces/IVaultFactory.sol"; import {Registry, RegistryFactory} from "@vault-periphery/registry/RegistryFactory.sol"; @@ -154,7 +154,7 @@ contract Setup is ExtendedTest { uint256 _totalAssets, uint256 _totalDebt, uint256 _totalIdle - ) public { + ) public view { uint256 _assets = _strategy.totalAssets(); uint256 _balance = ERC20(_strategy.asset()).balanceOf( address(_strategy) From f89a6463b7f81c32cb007049c7f377f51f915a5a Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 8 Apr 2024 13:11:31 -0600 Subject: [PATCH 10/16] forge install: tokenized-strategy cf791a6f2d360e5c33866c9f0de10e83085920e9 --- .gitmodules | 3 +++ lib/tokenized-strategy | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/tokenized-strategy diff --git a/.gitmodules b/.gitmodules index 494f6dd..cc8b597 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ [submodule "lib/yearn-vaults-v3"] path = lib/yearn-vaults-v3 url = https://github.com/yearn/yearn-vaults-v3 +[submodule "lib/tokenized-strategy"] + path = lib/tokenized-strategy + url = https://github.com/yearn/tokenized-strategy diff --git a/lib/tokenized-strategy b/lib/tokenized-strategy new file mode 160000 index 0000000..cf791a6 --- /dev/null +++ b/lib/tokenized-strategy @@ -0,0 +1 @@ +Subproject commit cf791a6f2d360e5c33866c9f0de10e83085920e9 From 10f626bac6f554e895674344b52dfe00ca820aad Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 8 Apr 2024 14:59:28 -0600 Subject: [PATCH 11/16] fix: tagged --- .gitmodules | 8 +- lib/tokenized-strategy | 1 - lib/yearn-vaults-v3 | 1 - src/interfaces/IBaseStrategy.sol | 30 ----- src/interfaces/IEvents.sol | 113 ------------------- src/interfaces/IFactory.sol | 6 - src/interfaces/IStrategy.sol | 7 -- src/interfaces/ITokenizedStrategy.sol | 156 -------------------------- test/mocks/MockTokenizedStrategy.sol | 82 ++++++++++++++ test/utils/Setup.sol | 6 +- 10 files changed, 85 insertions(+), 325 deletions(-) delete mode 160000 lib/tokenized-strategy delete mode 160000 lib/yearn-vaults-v3 delete mode 100644 src/interfaces/IBaseStrategy.sol delete mode 100644 src/interfaces/IEvents.sol delete mode 100644 src/interfaces/IFactory.sol delete mode 100644 src/interfaces/IStrategy.sol delete mode 100644 src/interfaces/ITokenizedStrategy.sol create mode 100644 test/mocks/MockTokenizedStrategy.sol diff --git a/.gitmodules b/.gitmodules index cc8b597..9d90e6c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,10 +13,4 @@ url = https://github.com/yearn/vault-periphery [submodule "lib/tokenized-strategy-periphery"] path = lib/tokenized-strategy-periphery - url = https://github.com/yearn/tokenized-strategy-periphery -[submodule "lib/yearn-vaults-v3"] - path = lib/yearn-vaults-v3 - url = https://github.com/yearn/yearn-vaults-v3 -[submodule "lib/tokenized-strategy"] - path = lib/tokenized-strategy - url = https://github.com/yearn/tokenized-strategy + url = https://github.com/yearn/tokenized-strategy-periphery \ No newline at end of file diff --git a/lib/tokenized-strategy b/lib/tokenized-strategy deleted file mode 160000 index cf791a6..0000000 --- a/lib/tokenized-strategy +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cf791a6f2d360e5c33866c9f0de10e83085920e9 diff --git a/lib/yearn-vaults-v3 b/lib/yearn-vaults-v3 deleted file mode 160000 index 9fbc614..0000000 --- a/lib/yearn-vaults-v3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9fbc614bbce9d7cbad42e284a15f0f43cf1a673f diff --git a/src/interfaces/IBaseStrategy.sol b/src/interfaces/IBaseStrategy.sol deleted file mode 100644 index a76dd50..0000000 --- a/src/interfaces/IBaseStrategy.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -interface IBaseStrategy { - function tokenizedStrategyAddress() external view returns (address); - - /*////////////////////////////////////////////////////////////// - IMMUTABLE FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function availableDepositLimit( - address _owner - ) external view returns (uint256); - - function availableWithdrawLimit( - address _owner - ) external view returns (uint256); - - function deployFunds(uint256 _assets) external; - - function freeFunds(uint256 _amount) external; - - function harvestAndReport() external returns (uint256); - - function tendThis(uint256 _totalIdle) external; - - function shutdownWithdraw(uint256 _amount) external; - - function tendTrigger() external view returns (bool, bytes memory); -} diff --git a/src/interfaces/IEvents.sol b/src/interfaces/IEvents.sol deleted file mode 100644 index c4046d9..0000000 --- a/src/interfaces/IEvents.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -interface IEvents { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Emitted when a strategy is shutdown. - */ - event StrategyShutdown(); - - /** - * @notice Emitted on the initialization of any new `strategy` that uses `asset` - * with this specific `apiVersion`. - */ - event NewTokenizedStrategy( - address indexed strategy, - address indexed asset, - string apiVersion - ); - - /** - * @notice Emitted when the strategy reports `profit` or `loss` and - * `performanceFees` and `protocolFees` are paid out. - */ - event Reported( - uint256 profit, - uint256 loss, - uint256 protocolFees, - uint256 performanceFees - ); - - /** - * @notice Emitted when the 'performanceFeeRecipient' address is - * updated to 'newPerformanceFeeRecipient'. - */ - event UpdatePerformanceFeeRecipient( - address indexed newPerformanceFeeRecipient - ); - - /** - * @notice Emitted when the 'keeper' address is updated to 'newKeeper'. - */ - event UpdateKeeper(address indexed newKeeper); - - /** - * @notice Emitted when the 'performanceFee' is updated to 'newPerformanceFee'. - */ - event UpdatePerformanceFee(uint16 newPerformanceFee); - - /** - * @notice Emitted when the 'management' address is updated to 'newManagement'. - */ - event UpdateManagement(address indexed newManagement); - - /** - * @notice Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'. - */ - event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); - - /** - * @notice Emitted when the 'profitMaxUnlockTime' is updated to 'newProfitMaxUnlockTime'. - */ - event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); - - /** - * @notice Emitted when the 'pendingManagement' address is updated to 'newPendingManagement'. - */ - event UpdatePendingManagement(address indexed newPendingManagement); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the `caller` has exchanged `assets` for `shares`, - * and transferred those `shares` to `owner`. - */ - event Deposit( - address indexed caller, - address indexed owner, - uint256 assets, - uint256 shares - ); - - /** - * @dev Emitted when the `caller` has exchanged `owner`s `shares` for `assets`, - * and transferred those `assets` to `receiver`. - */ - event Withdraw( - address indexed caller, - address indexed receiver, - address indexed owner, - uint256 assets, - uint256 shares - ); -} diff --git a/src/interfaces/IFactory.sol b/src/interfaces/IFactory.sol deleted file mode 100644 index 57a3930..0000000 --- a/src/interfaces/IFactory.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -interface IFactory { - function protocol_fee_config() external view returns (uint16, address); -} diff --git a/src/interfaces/IStrategy.sol b/src/interfaces/IStrategy.sol deleted file mode 100644 index 7c5a59a..0000000 --- a/src/interfaces/IStrategy.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -import {ITokenizedStrategy} from "./ITokenizedStrategy.sol"; -import {IBaseStrategy} from "./IBaseStrategy.sol"; - -interface IStrategy is IBaseStrategy, ITokenizedStrategy {} diff --git a/src/interfaces/ITokenizedStrategy.sol b/src/interfaces/ITokenizedStrategy.sol deleted file mode 100644 index 8e10d22..0000000 --- a/src/interfaces/ITokenizedStrategy.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.18; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; -import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; - -// Interface that implements the 4626 standard and the implementation functions -interface ITokenizedStrategy is IERC4626, IERC20Permit { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - event StrategyShutdown(); - - event NewTokenizedStrategy( - address indexed strategy, - address indexed asset, - string apiVersion - ); - - event Reported( - uint256 profit, - uint256 loss, - uint256 protocolFees, - uint256 performanceFees - ); - - event UpdatePerformanceFeeRecipient( - address indexed newPerformanceFeeRecipient - ); - - event UpdateKeeper(address indexed newKeeper); - - event UpdatePerformanceFee(uint16 newPerformanceFee); - - event UpdateManagement(address indexed newManagement); - - event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); - - event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); - - event UpdatePendingManagement(address indexed newPendingManagement); - - /*////////////////////////////////////////////////////////////// - INITIALIZATION - //////////////////////////////////////////////////////////////*/ - - function initialize( - address _asset, - string memory _name, - address _management, - address _performanceFeeRecipient, - address _keeper - ) external; - - /*////////////////////////////////////////////////////////////// - NON-STANDARD 4626 OPTIONS - //////////////////////////////////////////////////////////////*/ - - function withdraw( - uint256 assets, - address receiver, - address owner, - uint256 maxLoss - ) external returns (uint256); - - function redeem( - uint256 shares, - address receiver, - address owner, - uint256 maxLoss - ) external returns (uint256); - - /*////////////////////////////////////////////////////////////// - MODIFIER HELPERS - //////////////////////////////////////////////////////////////*/ - - function requireManagement(address _sender) external view; - - function requireKeeperOrManagement(address _sender) external view; - - function requireEmergencyAuthorized(address _sender) external view; - - /*////////////////////////////////////////////////////////////// - KEEPERS FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function tend() external; - - function report() external returns (uint256 _profit, uint256 _loss); - - /*////////////////////////////////////////////////////////////// - CONSTANTS - //////////////////////////////////////////////////////////////*/ - - function MAX_FEE() external view returns (uint16); - - function FACTORY() external view returns (address); - - /*////////////////////////////////////////////////////////////// - GETTERS - //////////////////////////////////////////////////////////////*/ - - function apiVersion() external view returns (string memory); - - function pricePerShare() external view returns (uint256); - - function management() external view returns (address); - - function pendingManagement() external view returns (address); - - function keeper() external view returns (address); - - function emergencyAdmin() external view returns (address); - - function performanceFee() external view returns (uint16); - - function performanceFeeRecipient() external view returns (address); - - function fullProfitUnlockDate() external view returns (uint256); - - function profitUnlockingRate() external view returns (uint256); - - function profitMaxUnlockTime() external view returns (uint256); - - function lastReport() external view returns (uint256); - - function isShutdown() external view returns (bool); - - function unlockedShares() external view returns (uint256); - - /*////////////////////////////////////////////////////////////// - SETTERS - //////////////////////////////////////////////////////////////*/ - - function setPendingManagement(address) external; - - function acceptManagement() external; - - function setKeeper(address _keeper) external; - - function setEmergencyAdmin(address _emergencyAdmin) external; - - function setPerformanceFee(uint16 _performanceFee) external; - - function setPerformanceFeeRecipient( - address _performanceFeeRecipient - ) external; - - function setProfitMaxUnlockTime(uint256 _profitMaxUnlockTime) external; - - function shutdownStrategy() external; - - function emergencyWithdraw(uint256 _amount) external; -} diff --git a/test/mocks/MockTokenizedStrategy.sol b/test/mocks/MockTokenizedStrategy.sol new file mode 100644 index 0000000..f7f595d --- /dev/null +++ b/test/mocks/MockTokenizedStrategy.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {TokenizedStrategy, ERC20} from "@tokenized-strategy/TokenizedStrategy.sol"; + +contract MockTokenizedStrategy is TokenizedStrategy { + + constructor( + address _factory, + address _asset, + string memory _name, + address _management, + address _keeper + ) TokenizedStrategy(_factory) { + // Cache storage pointer + StrategyData storage S = _strategyStorage(); + + // Set the strategy's underlying asset + S.asset = ERC20(_asset); + // Set the Strategy Tokens name. + S.name = _name; + // Set decimals based off the `asset`. + S.decimals = ERC20(_asset).decimals(); + + // Set last report to this block. + S.lastReport = uint96(block.timestamp); + + // Set the default management address. Can't be 0. + require(_management != address(0), "ZERO ADDRESS"); + S.management = _management; + S.performanceFeeRecipient = _management; + // Set the keeper address + S.keeper = _keeper; + } + + function availableWithdrawLimit( + address /*_owner*/ + ) public view virtual returns (uint256) { + return type(uint256).max; + } + + function deployFunds(uint256 _amount) external virtual {} + + function freeFunds(uint256 _amount) external virtual {} + + function harvestAndReport() external virtual returns (uint256) { + return _strategyStorage().asset.balanceOf(address(this)); + } +} + +contract MockTokenized is MockTokenizedStrategy { + uint256 public loss; + uint256 public limit; + + constructor( + address _factory, + address _asset, + string memory _name, + address _management, + address _keeper + ) MockTokenizedStrategy(_factory, _asset, _name, _management, _keeper) { + } + + function realizeLoss(uint256 _amount) external { + _strategyStorage().asset.transfer(msg.sender, _amount); + } + + function availableWithdrawLimit( + address _owner + ) public view virtual override returns (uint256) { + if (limit != 0) { + uint256 _totalAssets = _strategyStorage().totalAssets; + return _totalAssets > limit ? _totalAssets - limit : 0; + } else { + return super.availableWithdrawLimit(_owner); + } + } + + function setLimit(uint256 _limit) external { + limit = _limit; + } +} diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index 7e54a11..388784d 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -9,7 +9,7 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Roles} from "@yearn-vaults/interfaces/Roles.sol"; import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; -import {IStrategy} from "../../src/interfaces/IStrategy.sol"; +import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol"; import {IVaultFactory} from "@yearn-vaults/interfaces/IVaultFactory.sol"; import {Registry, RegistryFactory} from "@vault-periphery/registry/RegistryFactory.sol"; @@ -73,9 +73,7 @@ contract Setup is ExtendedTest { vm.label(performanceFeeRecipient, "performanceFeeRecipient"); } - function setupVault() public returns (IVault) { - - } + function setupVault() public returns (IVault) {} function setUpStrategy() public returns (IStrategy) { // we save the strategy as a IStrategyInterface to give it the needed interface From bcbe5156290d357f1ce943758213bc75132b8926 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 8 Apr 2024 15:00:16 -0600 Subject: [PATCH 12/16] forge install: tokenized-strategy v3.0.2-1 --- .gitmodules | 5 ++++- lib/tokenized-strategy | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 lib/tokenized-strategy diff --git a/.gitmodules b/.gitmodules index 9d90e6c..02aeeb6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,4 +13,7 @@ url = https://github.com/yearn/vault-periphery [submodule "lib/tokenized-strategy-periphery"] path = lib/tokenized-strategy-periphery - url = https://github.com/yearn/tokenized-strategy-periphery \ No newline at end of file + url = https://github.com/yearn/tokenized-strategy-periphery +[submodule "lib/tokenized-strategy"] + path = lib/tokenized-strategy + url = https://github.com/yearn/tokenized-strategy diff --git a/lib/tokenized-strategy b/lib/tokenized-strategy new file mode 160000 index 0000000..7bf1870 --- /dev/null +++ b/lib/tokenized-strategy @@ -0,0 +1 @@ +Subproject commit 7bf187015f5f7159276f80cd52204431ab1b3b8b From 8d94eb49d9a46deb8bfd7c9fe201cd0c9416518f Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Mon, 8 Apr 2024 15:00:29 -0600 Subject: [PATCH 13/16] forge install: yearn-vaults-v3 v3.0.2-1 --- .gitmodules | 3 +++ lib/yearn-vaults-v3 | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/yearn-vaults-v3 diff --git a/.gitmodules b/.gitmodules index 02aeeb6..3b05dc5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ [submodule "lib/tokenized-strategy"] path = lib/tokenized-strategy url = https://github.com/yearn/tokenized-strategy +[submodule "lib/yearn-vaults-v3"] + path = lib/yearn-vaults-v3 + url = https://github.com/yearn/yearn-vaults-v3 diff --git a/lib/yearn-vaults-v3 b/lib/yearn-vaults-v3 new file mode 160000 index 0000000..9fbc614 --- /dev/null +++ b/lib/yearn-vaults-v3 @@ -0,0 +1 @@ +Subproject commit 9fbc614bbce9d7cbad42e284a15f0f43cf1a673f From 0a4713167ef385dc365617e46feacd0c4a585ef9 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Tue, 16 Apr 2024 20:01:39 -0600 Subject: [PATCH 14/16] build: its alive --- .gitmodules | 4 +- Makefile | 1 - foundry.toml | 2 +- lib/tokenized-strategy | 1 - src/L1Deployer.sol | 358 +++++++- src/L1YearnEscrow.sol | 202 ++++- src/RoleManager.sol | 829 ++++++++++++++++++ src/interfaces/ICREATE3Factory.sol | 13 + .../Polygon/IPolygonRollupManager.sol | 120 +++ src/interfaces/Yearn/IAccountant.sol | 238 +++++ src/interfaces/Yearn/IAccountantFactory.sol | 32 + src/interfaces/Yearn/IBaseStrategy.sol | 30 + src/interfaces/Yearn/IEvents.sol | 113 +++ src/interfaces/Yearn/IStrategy.sol | 7 + src/interfaces/Yearn/ITokenizedStrategy.sol | 156 ++++ test/mocks/BaseStrategy.sol | 513 +++++++++++ test/mocks/MockTokenizedStrategy.sol | 57 +- test/setup.t.sol | 41 + test/utils/Setup.sol | 134 ++- 19 files changed, 2778 insertions(+), 73 deletions(-) delete mode 160000 lib/tokenized-strategy create mode 100644 src/RoleManager.sol create mode 100644 src/interfaces/ICREATE3Factory.sol create mode 100644 src/interfaces/Polygon/IPolygonRollupManager.sol create mode 100644 src/interfaces/Yearn/IAccountant.sol create mode 100644 src/interfaces/Yearn/IAccountantFactory.sol create mode 100644 src/interfaces/Yearn/IBaseStrategy.sol create mode 100644 src/interfaces/Yearn/IEvents.sol create mode 100644 src/interfaces/Yearn/IStrategy.sol create mode 100644 src/interfaces/Yearn/ITokenizedStrategy.sol create mode 100644 test/mocks/BaseStrategy.sol create mode 100644 test/setup.t.sol diff --git a/.gitmodules b/.gitmodules index 3b05dc5..6054525 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,9 +14,7 @@ [submodule "lib/tokenized-strategy-periphery"] path = lib/tokenized-strategy-periphery url = https://github.com/yearn/tokenized-strategy-periphery -[submodule "lib/tokenized-strategy"] - path = lib/tokenized-strategy - url = https://github.com/yearn/tokenized-strategy [submodule "lib/yearn-vaults-v3"] path = lib/yearn-vaults-v3 url = https://github.com/yearn/yearn-vaults-v3 + release = v3.0.2-1 \ No newline at end of file diff --git a/Makefile b/Makefile index 58e54a5..e2b83eb 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ inspect :; forge inspect ${contract} storage-layout --pretty FORK_URL := ${ETH_RPC_URL} -# local tests without fork test :; forge test -vv --fork-url ${FORK_URL} trace :; forge test -vvv --fork-url ${FORK_URL} gas :; forge test --fork-url ${FORK_URL} --gas-report diff --git a/foundry.toml b/foundry.toml index cb6f8cd..f44bb84 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,7 +9,7 @@ auto_detect_remappings = false remappings = [ 'forge-std/=lib/forge-std/src/', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', - "@tokenized-strategy/=lib/tokenized-strategy/src/", + "@tokenized-strategy/=src/", "@yearn-vaults/=lib/yearn-vaults-v3/contracts/", "ds-test/=lib/forge-std/lib/ds-test/src/", "@periphery/=lib/tokenized-strategy-periphery/src/", diff --git a/lib/tokenized-strategy b/lib/tokenized-strategy deleted file mode 160000 index 7bf1870..0000000 --- a/lib/tokenized-strategy +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7bf187015f5f7159276f80cd52204431ab1b3b8b diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index eedd576..0d7bda6 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -2,5 +2,361 @@ pragma solidity ^0.8.20; import {L1YearnEscrow} from "./L1YearnEscrow.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -contract L1Deployer {} +import {IPolygonZkEVMBridge} from "@zkevm-stb/interfaces/IPolygonZkEVMBridge.sol"; +import {IPolygonRollupManager, IPolygonRollupContract} from "./interfaces/Polygon/IPolygonRollupManager.sol"; + +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; +import {Proxy} from "@zkevm-stb/Proxy.sol"; + +import {RoleManager} from "./RoleManager.sol"; + +// TODO: +// 1. Deposit Limits/module +// create 3 factory +// External create3 Address getters + +/// Governace Structure: +// 1. GOVERNATOR Can change the Holders, Impl and addresses (Rare) 2/3 meta multisig (No Roles) +// 2. CZAR/DADDY Sets strategies All Roles +// 3. Management/SMS Day to Day Ops + +/// @title PolyYearn Stake the Bridge Role Manager. +contract L1Deployer is RoleManager { + event RegisteredNewRollup( + uint32 indexed rollupID, + address indexed rollupContract, + address indexed manager + ); + + event NewL1Escrow(uint32 indexed rollupID, address indexed l1Escrow); + + struct ChainConfig { + IPolygonRollupContract rollupContract; + address manager; + mapping(address => address) escrows; + } + + /// @notice Only allow either governance or the position holder to call. + modifier onlyChainAdmin(uint32 _rollupID) { + _isChainAdmin(_rollupID); + _; + } + + /// @notice Check if the msg sender is governance or the specified position holder. + function _isChainAdmin(uint32 _rollupID) internal view virtual { + require( + msg.sender == chainConfig[_rollupID].rollupContract.admin(), + "!admin" + ); + } + + ICREATE3Factory internal create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); + + /*////////////////////////////////////////////////////////////// + POSITION ID'S + //////////////////////////////////////////////////////////////*/ + + bytes32 public constant ESCROW_IMPLEMENTATION = + keccak256("Escrow Implementation"); + bytes32 public constant L2_DEPLOYER = keccak256("L2 Deployer"); + + uint256 public immutable originalID; + + address public immutable bridgeAddress; + + address public immutable rollupManager; + + /*////////////////////////////////////////////////////////////// + STORAGE + //////////////////////////////////////////////////////////////*/ + + /// @notice Mapping of chain ID to the rollup config. + mapping(uint32 => ChainConfig) public chainConfig; + + constructor( + address _governator, + address _czar, + address _management, + address _emergencyAdmin, + address _keeper, + address _registry, + address _rollupManager, + address _escrowImplementation + ) + RoleManager( + _governator, + _czar, + _management, + _emergencyAdmin, + _keeper, + _registry + ) + { + originalID = block.chainid; + bridgeAddress = IPolygonRollupManager(_rollupManager).bridgeAddress(); + rollupManager = _rollupManager; + + _positions[ESCROW_IMPLEMENTATION].holder = _escrowImplementation; + } + + function registerRollup( + uint32 _rollupID, + address _l1Manager + ) external virtual { + ChainConfig storage _chainConfig = chainConfig[_rollupID]; + require( + address(_chainConfig.rollupContract) == address(0), + "registered" + ); + require(_l1Manager != address(0), "ZERO ADDRESS"); + + IPolygonRollupContract _rollupContract = IPolygonRollupManager( + rollupManager + ).rollupIDToRollupData(_rollupID).rollupContract; + // Checks the rollup ID is valid and the caller is the rollup Admin. + require(msg.sender == _rollupContract.admin(), "!admin"); + + _chainConfig.rollupContract = _rollupContract; + _chainConfig.manager = _l1Manager; + + emit RegisteredNewRollup( + _rollupID, + address(_rollupContract), + _l1Manager + ); + } + + /*////////////////////////////////////////////////////////////// + ESCROW CREATION + //////////////////////////////////////////////////////////////*/ + + function newAsset( + uint32 _rollupID, + address _asset + ) external returns (address, address) { + // Verify the rollup Id is valid. + require( + address(chainConfig[_rollupID].rollupContract) != address(0), + "rollup not registered" + ); + return _newAsset(_rollupID, _asset, 0); + } + + function newAsset( + uint32 _rollupID, + address _asset, + uint256 _minimumBuffer + ) external onlyChainAdmin(_rollupID) returns (address, address) { + // Modifier passing implies a valid rollup ID. + return _newAsset(_rollupID, _asset, _minimumBuffer); + } + + function _newAsset( + uint32 _rollupID, + address _asset, + uint256 _minimumBuffer + ) internal virtual returns (address _vault, address _l1Escrow) { + // Verify that the vault is not already set for that chain. + _l1Escrow = getEscrow(_rollupID, _asset); + if (_l1Escrow != address(0)) revert AlreadyDeployed(_l1Escrow); + + // Check if there is a current default vault. + _vault = getVault(_asset); + + // If not, deploy one and do full setup + if (_vault == address(0)) { + _vault = _deployDefaultVault(_asset); + } + + // Deploy L1 Escrow. + _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault, _minimumBuffer); + } + + function newCustomAsset( + uint32 _rollupID, + address _asset + ) external virtual onlyChainAdmin(_rollupID) returns (address _vault) { + string memory _rollupIDString = Strings.toString(_rollupID); + + // Name is "{SYMBOL}-STB-{rollupID} yVault" + string memory _name = string( + abi.encodePacked( + ERC20(_asset).symbol(), + "-STB-", + _rollupIDString, + " yVault" + ) + ); + // Symbol is "stb{SYMBOL}-{rollupID}". + string memory _symbol = string( + abi.encodePacked( + "stb", + ERC20(_asset).symbol(), + "-", + _rollupIDString + ) + ); + + _vault = _newVault( + _asset, + _name, + _symbol, + _rollupID, + 2 ** 256 - 1, + defaultProfitMaxUnlock + ); + + // Custom Roles??? + _newCustomAsset(_rollupID, _asset, _vault); + } + + function newCustomAsset( + uint32 _rollupID, + address _asset, + address _vault + ) external virtual onlyChainAdmin(_rollupID) { + _addNewVault(_rollupID, _vault); + _newCustomAsset(_rollupID, _asset, _vault); + } + + function _newCustomAsset( + uint32 _rollupID, + address _asset, + address _vault + ) internal virtual { + address _l1Escrow = getEscrow(_rollupID, _asset); + + if (_l1Escrow == address(0)) { + _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault, 0); + } + + _assetToVault[_asset][_rollupID] = _vault; + } + + /*////////////////////////////////////////////////////////////// + VAULT CREATION + //////////////////////////////////////////////////////////////*/ + + function _deployDefaultVault( + address _asset + ) internal virtual returns (address) { + // Name is "{SYMBOL}-STB yVault" + string memory _name = string( + abi.encodePacked(ERC20(_asset).symbol(), "-STB yVault") + ); + // Symbol is "stb{SYMBOL}". + string memory _symbol = string( + abi.encodePacked("stb", ERC20(_asset).symbol()) + ); + + return + _newVault( + _asset, + _name, + _symbol, + DEFAULT_ID, + 2 ** 256 - 1, + defaultProfitMaxUnlock + ); + } + + function _deployL1Escrow( + uint32 _rollupID, + address _asset, + address _vault, + uint256 _minimumBuffer + ) internal returns (address _l1Escrow) { + ChainConfig storage _chainConfig = chainConfig[_rollupID]; + + bytes memory symbol = bytes(ERC20(_asset).symbol()); + + bytes memory data = abi.encodeWithSelector( + L1YearnEscrow.initialize.selector, + _chainConfig.rollupContract.admin(), + _chainConfig.manager, + bridgeAddress, + _getL2EscrowAddress(symbol), + _rollupID, + _asset, + _getL2TokenAddress(symbol), + _vault, + _minimumBuffer + ); + + bytes memory creationCode = abi.encodePacked( + type(Proxy).creationCode, + abi.encode(address(getPositionHolder(ESCROW_IMPLEMENTATION)), data) + ); + + _l1Escrow = create3Factory.deploy( + keccak256(abi.encodePacked(bytes("L1Escrow:"), symbol)), + creationCode + ); + + // Make sure we got the right address. + require(_l1Escrow == _getL1EscrowAddress(symbol), "wrong address"); + + // Set the mapping + _chainConfig.escrows[_asset] = _l1Escrow; + + // Send Message to Bridge for L2 + // TODO: Will L2 Deployer be the same each chain? + IPolygonZkEVMBridge(bridgeAddress).bridgeMessage( + _rollupID, + getPositionHolder(L2_DEPLOYER), + false, + abi.encode(_asset, _l1Escrow) + ); + + emit NewL1Escrow(_rollupID, _l1Escrow); + } + + function _getL1EscrowAddress( + bytes memory _symbol + ) internal returns (address) { + return + create3Factory.getDeployed( + address(this), + keccak256(abi.encodePacked(bytes("L1Escrow:"), _symbol)) + ); + } + + // Address will be the L2 deployer + function _getL2EscrowAddress( + bytes memory _symbol + ) internal returns (address) { + return + create3Factory.getDeployed( + getPositionHolder(L2_DEPLOYER), + keccak256(abi.encodePacked(bytes("L2Escrow:"), _symbol)) + ); + } + + function _getL2TokenAddress( + bytes memory _symbol + ) internal returns (address) { + return + create3Factory.getDeployed( + getPositionHolder(L2_DEPLOYER), + keccak256(abi.encode(bytes("L2Token:"), _symbol)) + ); + } + + /** + * @notice Get the L1 Escrow for a specific asset and chain ID. + * @dev This will return address(0) if one has not been added or deployed. + * @param _rollupID The rollup chain ID. + * @param _asset The underlying asset used. + * @return The Escrow for the specified `_asset` and `_rollupID`. + */ + function getEscrow( + uint32 _rollupID, + address _asset + ) public view virtual returns (address) { + return chainConfig[_rollupID].escrows[_asset]; + } +} diff --git a/src/L1YearnEscrow.sol b/src/L1YearnEscrow.sol index 13b9f1e..83bf772 100644 --- a/src/L1YearnEscrow.sol +++ b/src/L1YearnEscrow.sol @@ -2,5 +2,205 @@ pragma solidity ^0.8.20; import {L1Escrow} from "@zkevm-stb/L1Escrow.sol"; +import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; -contract L1YearnEscrow is L1Escrow {} +// ADD buffer +contract L1YearnEscrow is L1Escrow { + // **************************** + // * Events * + // ************************** + + /** + * @dev Emitted when the Vault is updated. + */ + event UpdateVaultAddress(address indexed newVaultAddress); + + /** + * @dev Emitted when the minimum buffer is updated. + */ + event UpdateMinimumBuffer(uint256 newMinimumBuffer); + + // **************************** + // * ERC-7201 Storage * + // ************************** + + /// @custom:storage-location erc7201:yearn.storage.vault + struct VaultStorage { + IVault vaultAddress; + uint256 minimumBuffer; + } + + // keccak256(abi.encode(uint256(keccak256("yearn.storage.vault")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant VaultStorageLocation = + 0xff1003c0fa1e6064b336121b432b179c1b66edc6a2d9068cade1ea1361605700; + + function _getVaultStorage() private pure returns (VaultStorage storage $) { + assembly { + $.slot := VaultStorageLocation + } + } + + function vaultAddress() public view returns (IVault) { + VaultStorage storage $ = _getVaultStorage(); + return $.vaultAddress; + } + + function minimumBuffer() public view returns (uint256) { + VaultStorage storage $ = _getVaultStorage(); + return $.minimumBuffer; + } + + // **************************** + // * Initializer * + // **************************** + + /** + * @notice L1YearnEscrow initializer + * @param _admin The admin address + * @param _manager The escrow manager address + * @param _polygonZkEVMBridge Polygon ZkEVM bridge address + * @param _counterpartContract Counterpart contract + * @param _counterpartNetwork Counterpart network + * @param _originTokenAddress Token address + * @param _wrappedTokenAddress L2Token address on Polygon ZkEVM + * @param _vaultAddress Address of the vault to use. + * @param _minimumBuffer Buffer if any to keep idle. + */ + function initialize( + address _admin, + address _manager, + address _polygonZkEVMBridge, + address _counterpartContract, + uint32 _counterpartNetwork, + address _originTokenAddress, + address _wrappedTokenAddress, + address _vaultAddress, + uint256 _minimumBuffer + ) public virtual initializer { + // Initialize the default escrow. + initialize( + _admin, + _manager, + _polygonZkEVMBridge, + _counterpartContract, + _counterpartNetwork, + _originTokenAddress, + _wrappedTokenAddress + ); + + VaultStorage storage $ = _getVaultStorage(); + // Set the vault variables + $.vaultAddress = IVault(_vaultAddress); + $.minimumBuffer = _minimumBuffer; + } + + // **************************** + // * Bridge * + // **************************** + + /** + * @dev Handle the reception of the tokens + * @param amount Token amount + */ + function _receiveTokens( + uint256 amount + ) internal virtual override whenNotPaused { + super._receiveTokens(amount); + VaultStorage storage $ = _getVaultStorage(); + uint256 _minimumBuffer = $.minimumBuffer; + // Deposit to the vault if above buffer + if (_minimumBuffer != 0) { + uint256 underlyingBalance = originTokenAddress().balanceOf( + address(this) + ); + if (underlyingBalance <= _minimumBuffer) { + return; + } + + unchecked { + amount = underlyingBalance - _minimumBuffer; + } + } + + $.vaultAddress.deposit(amount, address(this)); + } + + /** + * @dev Handle the transfer of the tokens + * @param destinationAddress Address destination that will receive the tokens on the other network + * @param amount Token amount + */ + function _transferTokens( + address destinationAddress, + uint256 amount + ) internal virtual override whenNotPaused { + // Withdraw from vault to receiver. + VaultStorage storage $ = _getVaultStorage(); + uint256 underlyingBalance = originTokenAddress().balanceOf( + address(this) + ); + if (underlyingBalance != 0) { + if (underlyingBalance > amount) { + super._transferTokens(destinationAddress, amount); + return; + } else { + super._transferTokens(destinationAddress, underlyingBalance); + unchecked { + amount = amount - underlyingBalance; + } + } + } + + $.vaultAddress.withdraw(amount, destinationAddress, address(this)); + } + + // **************************** + // * Manager * + // **************************** + + /** + * @dev Escrow manager can withdraw the token backing + * @param _recipient the recipient address + * @param _amount The amount of token + */ + function withdraw( + address _recipient, + uint256 _amount + ) external virtual override onlyRole(ESCROW_MANAGER_ROLE) whenNotPaused { + VaultStorage storage $ = _getVaultStorage(); + uint256 shares = $.vaultAddress.convertToShares(_amount); + $.vaultAddress.transfer(_recipient, shares); + emit Withdraw(_recipient, _amount); + } + + // **************************** + // * Admin * + // **************************** + + function updateVault( + address _vaultAddress + ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { + VaultStorage storage $ = _getVaultStorage(); + IVault oldVault = $.vaultAddress; + // If re-initializing to a new vault address. + if (address(oldVault) != address(0)) { + uint256 balance = oldVault.balanceOf(address(this)); + // Withdraw the full balance of the current vault. + if (balance != 0) { + oldVault.redeem(balance, address(this), address(this)); + } + } + $.vaultAddress = IVault(_vaultAddress); + + emit UpdateVaultAddress(_vaultAddress); + } + + function updateMinimumBuffer( + uint256 _minimumBuffer + ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { + VaultStorage storage $ = _getVaultStorage(); + $.minimumBuffer = _minimumBuffer; + + emit UpdateMinimumBuffer(_minimumBuffer); + } +} diff --git a/src/RoleManager.sol b/src/RoleManager.sol new file mode 100644 index 0000000..87733d8 --- /dev/null +++ b/src/RoleManager.sol @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +import {Roles} from "@yearn-vaults/interfaces/Roles.sol"; +import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; +import {IAccountant} from "./interfaces/Yearn/IAccountant.sol"; +import {Registry} from "@vault-periphery/registry/Registry.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {DebtAllocatorFactory} from "@vault-periphery/debtAllocators/DebtAllocatorFactory.sol"; + +/// @title PolyYearn Stake the Bridge Role Manager. +contract RoleManager { + /// @notice Revert message for when a contract has already been deployed. + error AlreadyDeployed(address _contract); + + /// @notice Emitted when a new vault has been deployed or added. + event AddedNewVault( + address indexed vault, + address indexed debtAllocator, + uint32 rollupID + ); + + /// @notice Emitted when a vaults debt allocator is updated. + event UpdateDebtAllocator( + address indexed vault, + address indexed debtAllocator + ); + + /// @notice Emitted when a new address is set for a position. + event UpdatePositionHolder( + bytes32 indexed position, + address indexed newAddress + ); + + /// @notice Emitted when a vault is removed. + event RemovedVault(address indexed vault); + + /// @notice Emitted when a new set of roles is set for a position + event UpdatePositionRoles(bytes32 indexed position, uint256 newRoles); + + /// @notice Emitted when the defaultProfitMaxUnlock variable is updated. + event UpdateDefaultProfitMaxUnlock(uint256 newDefaultProfitMaxUnlock); + + /// @notice Position struct + struct Position { + address holder; + uint96 roles; + } + + /// @notice Config that holds all vault info. + struct VaultConfig { + address asset; + uint32 rollupID; // 0 == default. + address debtAllocator; + uint256 index; + } + + /// @notice Only allow position holder to call. + modifier onlyPositionHolder(bytes32 _positionId) { + _isPositionHolder(_positionId); + _; + } + + /// @notice Check if the msg sender is specified position holder. + function _isPositionHolder(bytes32 _positionId) internal view virtual { + require(msg.sender == getPositionHolder(_positionId), "!allowed"); + } + + // Encoded name so that it can be held as a constant. + bytes32 internal constant _name_ = + bytes32(abi.encodePacked("Stake the Bridge Role Manager")); + + /// @notice Rollup ID to use for the default vaults. + uint32 internal constant DEFAULT_ID = 0; + + /*////////////////////////////////////////////////////////////// + POSITION ID'S + //////////////////////////////////////////////////////////////*/ + + /// @notice Position ID for "Czar". + bytes32 public constant CZAR = keccak256("Czar"); + /// @notice Position ID for "Keeper". + bytes32 public constant KEEPER = keccak256("Keeper"); + /// @notice Position ID for "Management". + bytes32 public constant MANAGEMENT = keccak256("Management"); + /// @notice Position ID for "Governator". + bytes32 public constant GOVERNATOR = keccak256("Governator"); + /// @notice Position ID for "Emergency Admin". + bytes32 public constant EMERGENCY_ADMIN = keccak256("Emergency Admin"); + /// @notice Position ID for "Pending Governator". + bytes32 public constant PENDING_GOVERNATOR = + keccak256("Pending Governator"); + + /// @notice Position ID for the Registry. + bytes32 public constant REGISTRY = keccak256("Registry"); + /// @notice Position ID for the Accountant. + bytes32 public constant ACCOUNTANT = keccak256("Accountant"); + /// @notice Position ID for Debt Allocator + bytes32 public constant DEBT_ALLOCATOR = keccak256("Debt Allocator"); + /// @notice Position ID for the Allocator Factory. + bytes32 public constant ALLOCATOR_FACTORY = keccak256("Allocator Factory"); + + /// @notice Immutable address that the RoleManager position + // will be transferred to when a vault is removed. + address public immutable chad; + + /*////////////////////////////////////////////////////////////// + STORAGE + //////////////////////////////////////////////////////////////*/ + + /// @notice Array storing addresses of all managed vaults. + address[] public vaults; + + /// @notice Default time until profits are fully unlocked for new vaults. + uint256 public defaultProfitMaxUnlock = 10 days; + + /// @notice Mapping of position ID to position information. + mapping(bytes32 => Position) internal _positions; + + /// @notice Mapping of vault addresses to its config. + mapping(address => VaultConfig) public vaultConfig; + + /// @notice Mapping of underlying asset => rollupID => vault address. + /// NOTE: We use 0 for the default vaults since that should never be a chain ID. + mapping(address => mapping(uint32 => address)) internal _assetToVault; + + constructor( + address _governator, + address _czar, + address _management, + address _emergencyAdmin, + address _keeper, + address _registry + ) { + chad = _czar; + + // Governator gets no roles. + _positions[GOVERNATOR].holder = _governator; + + // Czar gets all of the Roles. + _positions[CZAR] = Position({holder: _czar, roles: uint96(Roles.ALL)}); + + // Set up the initial role configs for each position. + _positions[MANAGEMENT] = Position({ + holder: _management, + roles: uint96( + Roles.REPORTING_MANAGER | + Roles.DEBT_MANAGER | + Roles.QUEUE_MANAGER | + Roles.DEPOSIT_LIMIT_MANAGER | + Roles.DEBT_PURCHASER + ) + }); + + // Emergency Admin can set the max debt for strategies to have. + _positions[EMERGENCY_ADMIN] = Position({ + holder: _emergencyAdmin, + roles: uint96(Roles.EMERGENCY_MANAGER) + }); + + // The keeper can process reports. + _positions[KEEPER] = Position({ + holder: _keeper, + roles: uint96(Roles.REPORTING_MANAGER) + }); + + // Debt allocators manage debt and also need to process reports. + _positions[DEBT_ALLOCATOR].roles = uint96( + Roles.REPORTING_MANAGER | Roles.DEBT_MANAGER + ); + + // Set the registry + _positions[REGISTRY].holder = _registry; + } + + /*////////////////////////////////////////////////////////////// + VAULT CREATION + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Creates a new endorsed vault. + * @param _asset Address of the underlying asset. + * @param _depositLimit The deposit limit to start the vault with. + * @param _profitMaxUnlockTime Time until profits are fully unlocked. + * @return _vault Address of the newly created vault. + */ + function _newVault( + address _asset, + string memory _name, + string memory _symbol, + uint32 _rollupID, + uint256 _depositLimit, + uint256 _profitMaxUnlockTime + ) internal virtual returns (address _vault) { + // Deploy through the registry so it is automatically endorsed. + _vault = Registry(getPositionHolder(REGISTRY)).newEndorsedVault( + _asset, + _name, + _symbol, + address(this), + _profitMaxUnlockTime + ); + + // Deploy a new debt allocator for the vault. + address _debtAllocator = _deployAllocator(_vault); + + // Give out roles on the new vault. + _sanctify(_vault, _debtAllocator); + + // Set up the accountant. + _setAccountant(_vault); + + if (_depositLimit != 0) { + _setDepositLimit(_vault, _depositLimit); + } + + // Add the vault config to the mapping. + vaultConfig[_vault] = VaultConfig({ + asset: _asset, + rollupID: _rollupID, + debtAllocator: _debtAllocator, + index: vaults.length + }); + + // Add the vault to the mapping. + _assetToVault[_asset][_rollupID] = _vault; + + // Add the vault to the array. + vaults.push(_vault); + + // Emit event for new vault. + emit AddedNewVault(_vault, _debtAllocator, _rollupID); + } + + /** + * @dev Deploys a debt allocator for the specified vault. + * @param _vault Address of the vault. + * @return _debtAllocator Address of the deployed debt allocator. + */ + function _deployAllocator( + address _vault + ) internal virtual returns (address _debtAllocator) { + address factory = getPositionHolder(ALLOCATOR_FACTORY); + + // If we have a factory set. + if (factory != address(0)) { + // Deploy a new debt allocator for the vault with Management as the gov. + _debtAllocator = DebtAllocatorFactory(factory).newDebtAllocator( + _vault + ); + } else { + // If no factory is set we should be using one central allocator. + _debtAllocator = getPositionHolder(DEBT_ALLOCATOR); + } + } + + /** + * @dev Assigns roles to the newly added vault. + * + * This will override any previously set roles for the holders. But not effect + * the roles held by other addresses. + * + * @param _vault Address of the vault to sanctify. + * @param _debtAllocator Address of the debt allocator for the vault. + */ + function _sanctify( + address _vault, + address _debtAllocator + ) internal virtual { + // Set the roles for the Czar. + _setRole(_vault, _positions[CZAR]); + + // Set the roles for Management. + _setRole(_vault, _positions[MANAGEMENT]); + + // Set the roles for EMERGENCY_ADMIN. + _setRole(_vault, _positions[EMERGENCY_ADMIN]); + + // Set the roles for the Keeper. + _setRole(_vault, _positions[KEEPER]); + + // Give the specific debt allocator its roles. + _setRole( + _vault, + Position(_debtAllocator, _positions[DEBT_ALLOCATOR].roles) + ); + } + + /** + * @dev Used internally to set the roles on a vault for a given position. + * Will not set the roles if the position holder is address(0). + * This does not check that the roles are !=0 because it is expected that + * the holder will be set to 0 if the position is not being used. + * + * @param _vault Address of the vault. + * @param _position Holder address and roles to set. + */ + function _setRole( + address _vault, + Position memory _position + ) internal virtual { + if (_position.holder != address(0)) { + IVault(_vault).set_role(_position.holder, uint256(_position.roles)); + } + } + + /** + * @dev Sets the accountant on the vault and adds the vault to the accountant. + * This temporarily gives the `ACCOUNTANT_MANAGER` role to this contract. + * @param _vault Address of the vault to set up the accountant for. + */ + function _setAccountant(address _vault) internal virtual { + // Get the current accountant. + address accountant = getPositionHolder(ACCOUNTANT); + + // If there is an accountant set. + if (accountant != address(0)) { + // Temporarily give this contract the ability to set the accountant. + IVault(_vault).add_role(address(this), Roles.ACCOUNTANT_MANAGER); + + // Set the account on the vault. + IVault(_vault).set_accountant(accountant); + + // Take away the role. + IVault(_vault).remove_role(address(this), Roles.ACCOUNTANT_MANAGER); + + // Whitelist the vault in the accountant. + IAccountant(accountant).addVault(_vault); + } + } + + /** + * @dev Used to set an initial deposit limit when a new vault is deployed. + * Any further updates to the limit will need to be done by an address that + * holds the `DEPOSIT_LIMIT_MANAGER` role. + * @param _vault Address of the newly deployed vault. + * @param _depositLimit The deposit limit to set. + */ + function _setDepositLimit( + address _vault, + uint256 _depositLimit + ) internal virtual { + // Temporarily give this contract the ability to set the deposit limit. + IVault(_vault).add_role(address(this), Roles.DEPOSIT_LIMIT_MANAGER); + + // Set the initial deposit limit on the vault. + IVault(_vault).set_deposit_limit(_depositLimit); + + // Take away the role. + IVault(_vault).remove_role(address(this), Roles.DEPOSIT_LIMIT_MANAGER); + } + + /*////////////////////////////////////////////////////////////// + VAULT MANAGEMENT + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Adds a new vault to the RoleManager with the specified category and debt allocator. + * @dev If not already endorsed this function will endorse the vault. + * @param _rollupID rollupID for the vault to use. + * @param _vault Address of the vault to be added. + */ + function _addNewVault(uint32 _rollupID, address _vault) public virtual { + // If not the current role manager. + if (IVault(_vault).role_manager() != address(this)) { + // Accept the position of role manager. + IVault(_vault).accept_role_manager(); + } + + // Deploy a new Debt Allocator. + address _debtAllocator = _deployAllocator(_vault); + + // Get the current registry. + address registry = getPositionHolder(REGISTRY); + + // Check if the vault has been endorsed yet in the registry. + if (!Registry(registry).isEndorsed(_vault)) { + // If not endorse it. + // NOTE: This will revert if adding a vault of an older version. + Registry(registry).endorseMultiStrategyVault(_vault); + } + + // Set the roles up. + _sanctify(_vault, _debtAllocator); + + // Only set an accountant if there is not one set yet. + if (IVault(_vault).accountant() == address(0)) { + _setAccountant(_vault); + } + + address _asset = IVault(_vault).asset(); + + // Add the vault config to the mapping. + vaultConfig[_vault] = VaultConfig({ + asset: _asset, + rollupID: _rollupID, + debtAllocator: _debtAllocator, + index: vaults.length + }); + + // Add the vault to the mapping. + _assetToVault[_asset][_rollupID] = _vault; + + // Add the vault to the array. + vaults.push(_vault); + + // Emit event. + emit AddedNewVault(_vault, _debtAllocator, _rollupID); + } + + /** + * @notice Update a `_vault`s debt allocator. + * @dev This will deploy a new allocator using the current + * allocator factory set. + * @param _vault Address of the vault to update the allocator for. + */ + function updateDebtAllocator( + address _vault + ) external virtual returns (address _newDebtAllocator) { + _newDebtAllocator = _deployAllocator(_vault); + updateDebtAllocator(_vault, _newDebtAllocator); + } + + /** + * @notice Update a `_vault`s debt allocator to a specified `_debtAllocator`. + * @param _vault Address of the vault to update the allocator for. + * @param _debtAllocator Address of the new debt allocator. + */ + function updateDebtAllocator( + address _vault, + address _debtAllocator + ) public virtual onlyPositionHolder(MANAGEMENT) { + // Make sure the vault has been added to the role manager. + require(vaultConfig[_vault].asset != address(0), "vault not added"); + + // Remove the roles from the old allocator. + _setRole(_vault, Position(vaultConfig[_vault].debtAllocator, 0)); + + // Give the new debt allocator the relevant roles. + _setRole( + _vault, + Position(_debtAllocator, _positions[DEBT_ALLOCATOR].roles) + ); + + // Update the vaults config. + vaultConfig[_vault].debtAllocator = _debtAllocator; + + // Emit event. + emit UpdateDebtAllocator(_vault, _debtAllocator); + } + + /** + * @notice Update a `_vault`s keeper to a specified `_keeper`. + * @param _vault Address of the vault to update the keeper for. + * @param _keeper Address of the new keeper. + */ + function updateKeeper( + address _vault, + address _keeper + ) external virtual onlyPositionHolder(MANAGEMENT) { + // Make sure the vault has been added to the role manager. + require(vaultConfig[_vault].asset != address(0), "vault not added"); + + // Remove the roles from the old keeper if active. + address defaultKeeper = getPositionHolder(KEEPER); + if ( + _keeper != defaultKeeper && IVault(_vault).roles(defaultKeeper) != 0 + ) { + _setRole(_vault, Position(defaultKeeper, 0)); + } + + // Give the new keeper the relevant roles. + _setRole(_vault, Position(_keeper, _positions[KEEPER].roles)); + } + + /** + * @notice Removes a vault from the RoleManager. + * @dev This will NOT un-endorse the vault from the registry. + * @param _vault Address of the vault to be removed. + */ + function removeVault( + address _vault + ) external virtual onlyPositionHolder(MANAGEMENT) { + // Get the vault specific config. + VaultConfig memory config = vaultConfig[_vault]; + // Make sure the vault has been added to the role manager. + require(config.asset != address(0), "vault not added"); + + // Transfer the role manager position. + IVault(_vault).transfer_role_manager(chad); + + // Address of the vault to replace it with. + address vaultToMove = vaults[vaults.length - 1]; + + // Move the last vault to the index of `_vault` + vaults[config.index] = vaultToMove; + vaultConfig[vaultToMove].index = config.index; + + // Remove the last item. + vaults.pop(); + + // Delete the vault from the mapping. + delete _assetToVault[config.asset][config.rollupID]; + + // Delete the config for `_vault`. + delete vaultConfig[_vault]; + + emit RemovedVault(_vault); + } + + /** + * @notice Removes a specific role(s) for a `_holder` from the `_vaults`. + * @dev Can be used to remove one specific role or multiple. + * @param _vaults Array of vaults to adjust. + * @param _holder Address who's having a role removed. + * @param _role The role or roles to remove from the `_holder`. + */ + function removeRoles( + address[] calldata _vaults, + address _holder, + uint256 _role + ) external virtual onlyPositionHolder(CZAR) { + address _vault; + for (uint256 i = 0; i < _vaults.length; ++i) { + _vault = _vaults[i]; + // Make sure the vault is added to this Role Manager. + require(vaultConfig[_vault].asset != address(0), "vault not added"); + + // Remove the role. + IVault(_vault).remove_role(_holder, _role); + } + } + + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Setter function for updating a positions roles. + * @param _position Identifier for the position. + * @param _newRoles New roles for the position. + */ + function setPositionRoles( + bytes32 _position, + uint256 _newRoles + ) external virtual onlyPositionHolder(GOVERNATOR) { + // Cannot change the debt allocator or keeper roles since holder can be updated. + require( + _position != DEBT_ALLOCATOR && _position != KEEPER, + "cannot update" + ); + _positions[_position].roles = uint96(_newRoles); + + emit UpdatePositionRoles(_position, _newRoles); + } + + /** + * @notice Setter function for updating a positions holder. + * @param _position Identifier for the position. + * @param _newHolder New address for position. + */ + function setPositionHolder( + bytes32 _position, + address _newHolder + ) external virtual onlyPositionHolder(GOVERNATOR) { + require(_position != GOVERNATOR, "!two step flow"); + _positions[_position].holder = _newHolder; + + emit UpdatePositionHolder(_position, _newHolder); + } + + /** + * @notice Sets the default time until profits are fully unlocked for new vaults. + * @param _newDefaultProfitMaxUnlock New value for defaultProfitMaxUnlock. + */ + function setDefaultProfitMaxUnlock( + uint256 _newDefaultProfitMaxUnlock + ) external virtual onlyPositionHolder(GOVERNATOR) { + defaultProfitMaxUnlock = _newDefaultProfitMaxUnlock; + + emit UpdateDefaultProfitMaxUnlock(_newDefaultProfitMaxUnlock); + } + + /** + * @notice Accept the Governator role. + * @dev Caller must be the Pending Governator. + */ + function acceptGovernator() + external + virtual + onlyPositionHolder(PENDING_GOVERNATOR) + { + // Set the Governator role to the caller. + _positions[GOVERNATOR].holder = msg.sender; + emit UpdatePositionHolder(GOVERNATOR, msg.sender); + + // Reset the Pending Governator. + _positions[PENDING_GOVERNATOR].holder = address(0); + emit UpdatePositionHolder(PENDING_GOVERNATOR, address(0)); + } + + /*////////////////////////////////////////////////////////////// + VIEW METHODS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Get the name of this contract. + */ + function name() external view virtual returns (string memory) { + return string(abi.encodePacked(_name_)); + } + + /** + * @notice Get all vaults that this role manager controls.. + * @return The full array of vault addresses. + */ + function getAllVaults() external view virtual returns (address[] memory) { + return vaults; + } + + /** + * @notice Get the default vault for a specific asset and chain ID. + * @dev This will return address(0) if one has not been added or deployed. + * @param _asset The underlying asset used. + * @return The default vault for the specified `_asset`. + */ + function getVault(address _asset) public view virtual returns (address) { + return _assetToVault[_asset][DEFAULT_ID]; + } + + /** + * @notice Get the vault for a specific asset and chain ID. + * @dev This will return address(0) if one has not been added or deployed. + * A `_rollupID` of 0 will return the default vault. + * @param _asset The underlying asset used. + * @param _rollupID The rollup chain ID or 0 for the default version. + * @return The vault for the specified `_asset` and `_rollupID`. + */ + function getVault( + address _asset, + uint32 _rollupID + ) public view virtual returns (address) { + return _assetToVault[_asset][_rollupID]; + } + + /** + * @notice Check if a vault is managed by this contract. + * @dev This will check if the `asset` variable in the struct has been + * set for an easy external view check. + * + * Does not check the vaults `role_manager` position since that can be set + * by anyone for a random vault. + * + * @param _vault Address of the vault to check. + * @return . The vaults role manager status. + */ + function isVaultsRoleManager( + address _vault + ) external view virtual returns (bool) { + return vaultConfig[_vault].asset != address(0); + } + + /** + * @notice Get the debt allocator for a specific vault. + * @dev Will return address(0) if the vault is not managed by this contract. + * @param _vault Address of the vault. + * @return . Address of the debt allocator if any. + */ + function getDebtAllocator( + address _vault + ) external view virtual returns (address) { + return vaultConfig[_vault].debtAllocator; + } + + /** + * @notice Get the address and roles given to a specific position. + * @param _positionId The position identifier. + * @return The address that holds that position. + * @return The roles given to the specified position. + */ + function getPosition( + bytes32 _positionId + ) public view virtual returns (address, uint256) { + Position memory _position = _positions[_positionId]; + return (_position.holder, uint256(_position.roles)); + } + + /** + * @notice Get the current address assigned to a specific position. + * @param _positionId The position identifier. + * @return The current address assigned to the specified position. + */ + function getPositionHolder( + bytes32 _positionId + ) public view virtual returns (address) { + return _positions[_positionId].holder; + } + + /** + * @notice Get the current roles given to a specific position ID. + * @param _positionId The position identifier. + * @return The current roles given to the specified position ID. + */ + function getPositionRoles( + bytes32 _positionId + ) public view virtual returns (uint256) { + return uint256(_positions[_positionId].roles); + } + + /** + * @notice Get the address assigned to the Czar. + * @return The address assigned to the Czar. + */ + function getCzar() external view virtual returns (address) { + return getPositionHolder(CZAR); + } + + /** + * @notice Get the address assigned to the Governator. + * @return The address assigned to the Governator. + */ + function getGovernator() external view virtual returns (address) { + return getPositionHolder(GOVERNATOR); + } + + /** + * @notice Get the address assigned to the Pending Governator. + * @return The address assigned to the Pending Governator. + */ + function getPendingGovernator() external view virtual returns (address) { + return getPositionHolder(PENDING_GOVERNATOR); + } + + /** + * @notice Get the address assigned to Management. + * @return The address assigned to Management. + */ + function getManagement() external view virtual returns (address) { + return getPositionHolder(MANAGEMENT); + } + + /** + * @notice Get the address assigned to the Emergency Admin position. + * @return The address assigned to the Emergency Admin position. + */ + function getEmergencyAdmin() external view virtual returns (address) { + return getPositionHolder(EMERGENCY_ADMIN); + } + + /** + * @notice Get the address assigned to the Keeper position. + * @return The address assigned to the Keeper position. + */ + function getKeeper() external view virtual returns (address) { + return getPositionHolder(KEEPER); + } + + /** + * @notice Get the address assigned to the accountant. + * @return The address assigned to the accountant. + */ + function getAccountant() external view virtual returns (address) { + return getPositionHolder(ACCOUNTANT); + } + + /** + * @notice Get the address assigned to the Registry. + * @return The address assigned to the Registry. + */ + function getRegistry() external view virtual returns (address) { + return getPositionHolder(REGISTRY); + } + + /** + * @notice Get the address assigned to be the debt allocator if any. + * @return The address assigned to be the debt allocator if any. + */ + function getDebtAllocator() external view virtual returns (address) { + return getPositionHolder(DEBT_ALLOCATOR); + } + + /** + * @notice Get the address assigned to the allocator factory. + * @return The address assigned to the allocator factory. + */ + function getAllocatorFactory() external view virtual returns (address) { + return getPositionHolder(ALLOCATOR_FACTORY); + } + + /** + * @notice Get the roles given to the Czar position. + * @return The roles given to the Czar position. + */ + function getCzarRoles() external view virtual returns (uint256) { + return getPositionRoles(CZAR); + } + + /** + * @notice Get the roles given to the Management position. + * @return The roles given to the Management position. + */ + function getManagementRoles() external view virtual returns (uint256) { + return getPositionRoles(MANAGEMENT); + } + + /** + * @notice Get the roles given to the Emergency Admin position. + * @return The roles given to the Emergency Admin position. + */ + function getEmergencyAdminRoles() external view virtual returns (uint256) { + return getPositionRoles(EMERGENCY_ADMIN); + } + + /** + * @notice Get the roles given to the Keeper position. + * @return The roles given to the Keeper position. + */ + function getKeeperRoles() external view virtual returns (uint256) { + return getPositionRoles(KEEPER); + } + + /** + * @notice Get the roles given to the debt allocators. + * @return The roles given to the debt allocators. + */ + function getDebtAllocatorRoles() external view virtual returns (uint256) { + return getPositionRoles(DEBT_ALLOCATOR); + } +} diff --git a/src/interfaces/ICREATE3Factory.sol b/src/interfaces/ICREATE3Factory.sol new file mode 100644 index 0000000..41eaba9 --- /dev/null +++ b/src/interfaces/ICREATE3Factory.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +interface ICREATE3Factory { + function getDeployed( + address deployer, + bytes32 salt + ) external returns (address); + function deploy( + bytes32 salt, + bytes memory creationCode + ) external payable returns (address deployed); +} diff --git a/src/interfaces/Polygon/IPolygonRollupManager.sol b/src/interfaces/Polygon/IPolygonRollupManager.sol new file mode 100644 index 0000000..36cb06f --- /dev/null +++ b/src/interfaces/Polygon/IPolygonRollupManager.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +interface IPolygonRollupManager { + /** + * @notice Struct which will be stored for every batch sequence + * @param accInputHash Hash chain that contains all the information to process a batch: + * Before etrog: keccak256(bytes32 oldAccInputHash, keccak256(bytes transactions), bytes32 globalExitRoot, uint64 timestamp, address seqAddress) + * Etrog: keccak256(bytes32 oldAccInputHash, keccak256(bytes transactions), bytes32 l1InfoRoot/forcedGlobalExitRoot, uint64 currentTimestamp/forcedTimestamp, address l2Coinbase, bytes32 0/forcedBlockHashL1) + * @param sequencedTimestamp Sequenced timestamp + * @param previousLastBatchSequenced Previous last batch sequenced before the current one, this is used to properly calculate the fees + */ + struct SequencedBatchData { + bytes32 accInputHash; + uint64 sequencedTimestamp; + uint64 previousLastBatchSequenced; + } + + /** + * @notice Struct to store the pending states + * Pending state will be an intermediary state, that after a timeout can be consolidated, which means that will be added + * to the state root mapping, and the global exit root will be updated + * This is a protection mechanism against soundness attacks, that will be turned off in the future + * @param timestamp Timestamp where the pending state is added to the queue + * @param lastVerifiedBatch Last batch verified batch of this pending state + * @param exitRoot Pending exit root + * @param stateRoot Pending state root + */ + struct PendingState { + uint64 timestamp; + uint64 lastVerifiedBatch; + bytes32 exitRoot; + bytes32 stateRoot; + } + + /** + * @notice Struct which to store the rollup data of each chain + * @param rollupContract Rollup consensus contract, which manages everything + * related to sequencing transactions + * @param chainID Chain ID of the rollup + * @param verifier Verifier contract + * @param forkID ForkID of the rollup + * @param batchNumToStateRoot State root mapping + * @param sequencedBatches Queue of batches that defines the virtual state + * @param pendingStateTransitions Pending state mapping + * @param lastLocalExitRoot Last exit root verified, used for compute the rollupExitRoot + * @param lastBatchSequenced Last batch sent by the consensus contract + * @param lastVerifiedBatch Last batch verified + * @param lastPendingState Last pending state + * @param lastPendingStateConsolidated Last pending state consolidated + * @param lastVerifiedBatchBeforeUpgrade Last batch verified before the last upgrade + * @param rollupTypeID Rollup type ID, can be 0 if it was added as an existing rollup + * @param rollupCompatibilityID Rollup ID used for compatibility checks when upgrading + */ + struct RollupData { + IPolygonRollupContract rollupContract; + uint64 chainID; + IVerifierRollup verifier; + uint64 forkID; + //mapping(uint64 batchNum => bytes32) batchNumToStateRoot; + //mapping(uint64 batchNum => SequencedBatchData) sequencedBatches; + //mapping(uint256 pendingStateNum => PendingState) pendingStateTransitions; + bytes32 lastLocalExitRoot; + uint64 lastBatchSequenced; + uint64 lastVerifiedBatch; + uint64 lastPendingState; + uint64 lastPendingStateConsolidated; + uint64 lastVerifiedBatchBeforeUpgrade; + uint64 rollupTypeID; + uint8 rollupCompatibilityID; + } + + function bridgeAddress() external view returns (address); + + // Chain ID mapping for nullifying + function chainIDToRollupID( + uint64 chainID + ) external view returns (uint32 rollupID); + + // Rollups ID mapping + function rollupIDToRollupData( + uint32 rollupID + ) external view returns (RollupData memory); + + // Rollups address mapping + function rollupAddressToID( + address rollupAddress + ) external view returns (uint32 rollupID); +} + +interface IPolygonRollupBase { + function initialize( + address _admin, + address sequencer, + uint32 networkID, + address gasTokenAddress, + string memory sequencerURL, + string memory _networkName + ) external; + + function onVerifyBatches( + uint64 lastVerifiedBatch, + bytes32 newStateRoot, + address aggregator + ) external; +} + +interface IPolygonRollupContract is IPolygonRollupBase { + function admin() external view returns (address); +} + +/** + * @dev Define interface verifier + */ +interface IVerifierRollup { + function verifyProof( + bytes32[24] calldata proof, + uint256[1] calldata pubSignals + ) external view returns (bool); +} diff --git a/src/interfaces/Yearn/IAccountant.sol b/src/interfaces/Yearn/IAccountant.sol new file mode 100644 index 0000000..2a0c342 --- /dev/null +++ b/src/interfaces/Yearn/IAccountant.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GNU AGPLv3 +pragma solidity >=0.8.18; + +/// @title IAccountant. +interface IAccountant { + /// @notice An event emitted when a vault is added or removed. + event VaultChanged(address indexed vault, ChangeType change); + + /// @notice An event emitted when the default fee configuration is updated. + event UpdateDefaultFeeConfig(Fee defaultFeeConfig); + + /// @notice An event emitted when the future fee manager is set. + event SetFutureFeeManager(address indexed futureFeeManager); + + /// @notice An event emitted when a new fee manager is accepted. + event NewFeeManager(address indexed feeManager); + + /// @notice An event emitted when a new vault manager is set. + event UpdateVaultManager(address indexed newVaultManager); + + /// @notice An event emitted when the fee recipient is updated. + event UpdateFeeRecipient( + address indexed oldFeeRecipient, + address indexed newFeeRecipient + ); + + /// @notice An event emitted when a custom fee configuration is updated. + event UpdateCustomFeeConfig(address indexed vault, Fee custom_config); + + /// @notice An event emitted when a custom fee configuration is removed. + event RemovedCustomFeeConfig(address indexed vault); + + /// @notice An event emitted when the `maxLoss` parameter is updated. + event UpdateMaxLoss(uint256 maxLoss); + + /// @notice An event emitted when rewards are distributed. + event DistributeRewards(address indexed token, uint256 rewards); + + /// @notice Enum defining change types (added or removed). + enum ChangeType { + NULL, + ADDED, + REMOVED + } + + /// @notice Struct representing fee details. + struct Fee { + uint16 managementFee; // Annual management fee to charge. + uint16 performanceFee; // Performance fee to charge. + uint16 refundRatio; // Refund ratio to give back on losses. + uint16 maxFee; // Max fee allowed as a percent of gain. + uint16 maxGain; // Max percent gain a strategy can report. + uint16 maxLoss; // Max percent loss a strategy can report. + bool custom; // Flag to set for custom configs. + } + + /// @notice The amount of max loss to use when redeeming from vaults. + function maxLoss() external view returns (uint256); + + /// @notice The address of the fee manager. + function feeManager() external view returns (address); + + /// @notice The address of the fee recipient. + function feeRecipient() external view returns (address); + + /// @notice An address that can add or remove vaults. + function vaultManager() external view returns (address); + + /// @notice The address of the future fee manager. + function futureFeeManager() external view returns (address); + + /// @notice The default fee configuration. + function defaultConfig() external view returns (Fee memory); + + /// @notice Mapping to track added vaults. + function vaults(address) external view returns (bool); + + /// @notice Mapping vault => custom Fee config if any. + function customConfig(address) external view returns (Fee memory); + + /// @notice Mapping vault => strategy => flag for one time healthcheck skips. + function skipHealthCheck(address, address) external view returns (bool); + + /** + * @notice Called by a vault when a `strategy` is reporting. + * @dev The msg.sender must have been added to the `vaults` mapping. + * @param strategy Address of the strategy reporting. + * @param gain Amount of the gain if any. + * @param loss Amount of the loss if any. + * @return totalFees if any to charge. + * @return totalRefunds if any for the vault to pull. + */ + function report( + address strategy, + uint256 gain, + uint256 loss + ) external returns (uint256 totalFees, uint256 totalRefunds); + + /** + * @notice Function to add a new vault for this accountant to charge fees for. + * @dev This is not used to set any of the fees for the specific vault or strategy. Each fee will be set separately. + * @param vault The address of a vault to allow to use this accountant. + */ + function addVault(address vault) external; + + /** + * @notice Function to remove a vault from this accountant's fee charging list. + * @param vault The address of the vault to be removed from this accountant. + */ + function removeVault(address vault) external; + /** + * @notice Function to update the default fee configuration used for + all strategies that don't have a custom config set. + * @param defaultManagement Default annual management fee to charge. + * @param defaultPerformance Default performance fee to charge. + * @param defaultRefund Default refund ratio to give back on losses. + * @param defaultMaxFee Default max fee to allow as a percent of gain. + * @param defaultMaxGain Default max percent gain a strategy can report. + * @param defaultMaxLoss Default max percent loss a strategy can report. + */ + function updateDefaultConfig( + uint16 defaultManagement, + uint16 defaultPerformance, + uint16 defaultRefund, + uint16 defaultMaxFee, + uint16 defaultMaxGain, + uint16 defaultMaxLoss + ) external; + + /** + * @notice Function to set a custom fee configuration for a specific vault. + * @param vault The vault the strategy is hooked up to. + * @param customManagement Custom annual management fee to charge. + * @param customPerformance Custom performance fee to charge. + * @param customRefund Custom refund ratio to give back on losses. + * @param customMaxFee Custom max fee to allow as a percent of gain. + * @param customMaxGain Custom max percent gain a strategy can report. + * @param customMaxLoss Custom max percent loss a strategy can report. + */ + function setCustomConfig( + address vault, + uint16 customManagement, + uint16 customPerformance, + uint16 customRefund, + uint16 customMaxFee, + uint16 customMaxGain, + uint16 customMaxLoss + ) external; + + /** + * @notice Function to remove a previously set custom fee configuration for a vault. + * @param vault The vault to remove custom setting for. + */ + function removeCustomConfig(address vault) external; + + /** + * @notice Turn off the health check for a specific `vault` `strategy` combo. + * @dev This will only last for one report and get automatically turned back on. + * @param vault Address of the vault. + * @param strategy Address of the strategy. + */ + function turnOffHealthCheck(address vault, address strategy) external; + + /** + * @notice Public getter to check for custom setting. + * @dev We use uint256 for the flag since its cheaper so this + * will convert it to a bool for easy view functions. + * + * @param vault Address of the vault. + * @return If a custom fee config is set. + */ + function useCustomConfig(address vault) external view returns (bool); + + /** + * @notice Get the full config used for a specific `vault`. + * @param vault Address of the vault. + * @return fee The config that would be used during the report. + */ + function getVaultConfig( + address vault + ) external view returns (Fee memory fee); + + /** + * @notice Function to redeem the underlying asset from a vault. + * @dev Will default to using the full balance of the vault. + * @param vault The vault to redeem from. + */ + function redeemUnderlying(address vault) external; + + /** + * @notice Function to redeem the underlying asset from a vault. + * @param vault The vault to redeem from. + * @param amount The amount in vault shares to redeem. + */ + function redeemUnderlying(address vault, uint256 amount) external; + + /** + * @notice Sets the `maxLoss` parameter to be used on redeems. + * @param _maxLoss The amount in basis points to set as the maximum loss. + */ + function setMaxLoss(uint256 _maxLoss) external; + + /** + * @notice Function to distribute all accumulated fees to the designated recipient. + * @param token The token to distribute. + */ + function distribute(address token) external; + + /** + * @notice Function to distribute accumulated fees to the designated recipient. + * @param token The token to distribute. + * @param amount amount of token to distribute. + */ + function distribute(address token, uint256 amount) external; + + /** + * @notice Function to set a future fee manager address. + * @param _futureFeeManager The address to set as the future fee manager. + */ + function setFutureFeeManager(address _futureFeeManager) external; + + /** + * @notice Function to accept the role change and become the new fee manager. + * @dev This function allows the future fee manager to accept the role change and become the new fee manager. + */ + function acceptFeeManager() external; + /** + * @notice Function to set a new vault manager. + * @param newVaultManager Address to add or remove vaults. + */ + function setVaultManager(address newVaultManager) external; + + /** + * @notice Function to set a new address to receive distributed rewards. + * @param newFeeRecipient Address to receive distributed fees. + */ + function setFeeRecipient(address newFeeRecipient) external; +} diff --git a/src/interfaces/Yearn/IAccountantFactory.sol b/src/interfaces/Yearn/IAccountantFactory.sol new file mode 100644 index 0000000..0a10f56 --- /dev/null +++ b/src/interfaces/Yearn/IAccountantFactory.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GNU AGPLv3 +pragma solidity >=0.8.18; + +/** + * @title IAccountantFactory + */ +interface IAccountantFactory { + event NewAccountant(address indexed newAccountant); + + /** + * @dev Deploys a new Accountant contract with specified fee configurations and addresses + * @param feeManager The address to receive management and performance fees + * @param feeRecipient The address to receive refund fees + * @param defaultManagement Default management fee + * @param defaultPerformance Default performance fee + * @param defaultRefund Default refund ratio + * @param defaultMaxFee Default maximum fee + * @param defaultMaxGain Default maximum gain + * @param defaultMaxLoss Default maximum loss + * @return _newAccountant The address of the newly deployed Accountant contract + */ + function newAccountant( + address feeManager, + address feeRecipient, + uint16 defaultManagement, + uint16 defaultPerformance, + uint16 defaultRefund, + uint16 defaultMaxFee, + uint16 defaultMaxGain, + uint16 defaultMaxLoss + ) external returns (address _newAccountant); +} diff --git a/src/interfaces/Yearn/IBaseStrategy.sol b/src/interfaces/Yearn/IBaseStrategy.sol new file mode 100644 index 0000000..a76dd50 --- /dev/null +++ b/src/interfaces/Yearn/IBaseStrategy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +interface IBaseStrategy { + function tokenizedStrategyAddress() external view returns (address); + + /*////////////////////////////////////////////////////////////// + IMMUTABLE FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function availableDepositLimit( + address _owner + ) external view returns (uint256); + + function availableWithdrawLimit( + address _owner + ) external view returns (uint256); + + function deployFunds(uint256 _assets) external; + + function freeFunds(uint256 _amount) external; + + function harvestAndReport() external returns (uint256); + + function tendThis(uint256 _totalIdle) external; + + function shutdownWithdraw(uint256 _amount) external; + + function tendTrigger() external view returns (bool, bytes memory); +} diff --git a/src/interfaces/Yearn/IEvents.sol b/src/interfaces/Yearn/IEvents.sol new file mode 100644 index 0000000..c4046d9 --- /dev/null +++ b/src/interfaces/Yearn/IEvents.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +interface IEvents { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emitted when a strategy is shutdown. + */ + event StrategyShutdown(); + + /** + * @notice Emitted on the initialization of any new `strategy` that uses `asset` + * with this specific `apiVersion`. + */ + event NewTokenizedStrategy( + address indexed strategy, + address indexed asset, + string apiVersion + ); + + /** + * @notice Emitted when the strategy reports `profit` or `loss` and + * `performanceFees` and `protocolFees` are paid out. + */ + event Reported( + uint256 profit, + uint256 loss, + uint256 protocolFees, + uint256 performanceFees + ); + + /** + * @notice Emitted when the 'performanceFeeRecipient' address is + * updated to 'newPerformanceFeeRecipient'. + */ + event UpdatePerformanceFeeRecipient( + address indexed newPerformanceFeeRecipient + ); + + /** + * @notice Emitted when the 'keeper' address is updated to 'newKeeper'. + */ + event UpdateKeeper(address indexed newKeeper); + + /** + * @notice Emitted when the 'performanceFee' is updated to 'newPerformanceFee'. + */ + event UpdatePerformanceFee(uint16 newPerformanceFee); + + /** + * @notice Emitted when the 'management' address is updated to 'newManagement'. + */ + event UpdateManagement(address indexed newManagement); + + /** + * @notice Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'. + */ + event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); + + /** + * @notice Emitted when the 'profitMaxUnlockTime' is updated to 'newProfitMaxUnlockTime'. + */ + event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); + + /** + * @notice Emitted when the 'pendingManagement' address is updated to 'newPendingManagement'. + */ + event UpdatePendingManagement(address indexed newPendingManagement); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the `caller` has exchanged `assets` for `shares`, + * and transferred those `shares` to `owner`. + */ + event Deposit( + address indexed caller, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Emitted when the `caller` has exchanged `owner`s `shares` for `assets`, + * and transferred those `assets` to `receiver`. + */ + event Withdraw( + address indexed caller, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); +} diff --git a/src/interfaces/Yearn/IStrategy.sol b/src/interfaces/Yearn/IStrategy.sol new file mode 100644 index 0000000..7c5a59a --- /dev/null +++ b/src/interfaces/Yearn/IStrategy.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {ITokenizedStrategy} from "./ITokenizedStrategy.sol"; +import {IBaseStrategy} from "./IBaseStrategy.sol"; + +interface IStrategy is IBaseStrategy, ITokenizedStrategy {} diff --git a/src/interfaces/Yearn/ITokenizedStrategy.sol b/src/interfaces/Yearn/ITokenizedStrategy.sol new file mode 100644 index 0000000..8e10d22 --- /dev/null +++ b/src/interfaces/Yearn/ITokenizedStrategy.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; + +// Interface that implements the 4626 standard and the implementation functions +interface ITokenizedStrategy is IERC4626, IERC20Permit { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event StrategyShutdown(); + + event NewTokenizedStrategy( + address indexed strategy, + address indexed asset, + string apiVersion + ); + + event Reported( + uint256 profit, + uint256 loss, + uint256 protocolFees, + uint256 performanceFees + ); + + event UpdatePerformanceFeeRecipient( + address indexed newPerformanceFeeRecipient + ); + + event UpdateKeeper(address indexed newKeeper); + + event UpdatePerformanceFee(uint16 newPerformanceFee); + + event UpdateManagement(address indexed newManagement); + + event UpdateEmergencyAdmin(address indexed newEmergencyAdmin); + + event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime); + + event UpdatePendingManagement(address indexed newPendingManagement); + + /*////////////////////////////////////////////////////////////// + INITIALIZATION + //////////////////////////////////////////////////////////////*/ + + function initialize( + address _asset, + string memory _name, + address _management, + address _performanceFeeRecipient, + address _keeper + ) external; + + /*////////////////////////////////////////////////////////////// + NON-STANDARD 4626 OPTIONS + //////////////////////////////////////////////////////////////*/ + + function withdraw( + uint256 assets, + address receiver, + address owner, + uint256 maxLoss + ) external returns (uint256); + + function redeem( + uint256 shares, + address receiver, + address owner, + uint256 maxLoss + ) external returns (uint256); + + /*////////////////////////////////////////////////////////////// + MODIFIER HELPERS + //////////////////////////////////////////////////////////////*/ + + function requireManagement(address _sender) external view; + + function requireKeeperOrManagement(address _sender) external view; + + function requireEmergencyAuthorized(address _sender) external view; + + /*////////////////////////////////////////////////////////////// + KEEPERS FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function tend() external; + + function report() external returns (uint256 _profit, uint256 _loss); + + /*////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////*/ + + function MAX_FEE() external view returns (uint16); + + function FACTORY() external view returns (address); + + /*////////////////////////////////////////////////////////////// + GETTERS + //////////////////////////////////////////////////////////////*/ + + function apiVersion() external view returns (string memory); + + function pricePerShare() external view returns (uint256); + + function management() external view returns (address); + + function pendingManagement() external view returns (address); + + function keeper() external view returns (address); + + function emergencyAdmin() external view returns (address); + + function performanceFee() external view returns (uint16); + + function performanceFeeRecipient() external view returns (address); + + function fullProfitUnlockDate() external view returns (uint256); + + function profitUnlockingRate() external view returns (uint256); + + function profitMaxUnlockTime() external view returns (uint256); + + function lastReport() external view returns (uint256); + + function isShutdown() external view returns (bool); + + function unlockedShares() external view returns (uint256); + + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + + function setPendingManagement(address) external; + + function acceptManagement() external; + + function setKeeper(address _keeper) external; + + function setEmergencyAdmin(address _emergencyAdmin) external; + + function setPerformanceFee(uint16 _performanceFee) external; + + function setPerformanceFeeRecipient( + address _performanceFeeRecipient + ) external; + + function setProfitMaxUnlockTime(uint256 _profitMaxUnlockTime) external; + + function shutdownStrategy() external; + + function emergencyWithdraw(uint256 _amount) external; +} diff --git a/test/mocks/BaseStrategy.sol b/test/mocks/BaseStrategy.sol new file mode 100644 index 0000000..5ddd638 --- /dev/null +++ b/test/mocks/BaseStrategy.sol @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// TokenizedStrategy interface used for internal view delegateCalls. +import {ITokenizedStrategy} from "../../src/interfaces/Yearn/ITokenizedStrategy.sol"; + +/** + * @title YearnV3 Base Strategy + * @author yearn.finance + * @notice + * BaseStrategy implements all of the required functionality to + * seamlessly integrate with the `TokenizedStrategy` implementation contract + * allowing anyone to easily build a fully permissionless ERC-4626 compliant + * Vault by inheriting this contract and overriding three simple functions. + + * It utilizes an immutable proxy pattern that allows the BaseStrategy + * to remain simple and small. All standard logic is held within the + * `TokenizedStrategy` and is reused over any n strategies all using the + * `fallback` function to delegatecall the implementation so that strategists + * can only be concerned with writing their strategy specific code. + * + * This contract should be inherited and the three main abstract methods + * `_deployFunds`, `_freeFunds` and `_harvestAndReport` implemented to adapt + * the Strategy to the particular needs it has to generate yield. There are + * other optional methods that can be implemented to further customize + * the strategy if desired. + * + * All default storage for the strategy is controlled and updated by the + * `TokenizedStrategy`. The implementation holds a storage struct that + * contains all needed global variables in a manual storage slot. This + * means strategists can feel free to implement their own custom storage + * variables as they need with no concern of collisions. All global variables + * can be viewed within the Strategy by a simple call using the + * `TokenizedStrategy` variable. IE: TokenizedStrategy.globalVariable();. + */ +abstract contract BaseStrategy { + /*////////////////////////////////////////////////////////////// + MODIFIERS + //////////////////////////////////////////////////////////////*/ + /** + * @dev Used on TokenizedStrategy callback functions to make sure it is post + * a delegateCall from this address to the TokenizedStrategy. + */ + modifier onlySelf() { + _onlySelf(); + _; + } + + /** + * @dev Use to assure that the call is coming from the strategies management. + */ + modifier onlyManagement() { + TokenizedStrategy.requireManagement(msg.sender); + _; + } + + /** + * @dev Use to assure that the call is coming from either the strategies + * management or the keeper. + */ + modifier onlyKeepers() { + TokenizedStrategy.requireKeeperOrManagement(msg.sender); + _; + } + + /** + * @dev Use to assure that the call is coming from either the strategies + * management or the emergency admin. + */ + modifier onlyEmergencyAuthorized() { + TokenizedStrategy.requireEmergencyAuthorized(msg.sender); + _; + } + + /** + * @dev Require that the msg.sender is this address. + */ + function _onlySelf() internal view { + require(msg.sender == address(this), "!self"); + } + + /*////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////*/ + + /** + * @dev This is the address of the TokenizedStrategy implementation + * contract that will be used by all strategies to handle the + * accounting, logic, storage etc. + * + * Any external calls to the that don't hit one of the functions + * defined in this base or the strategy will end up being forwarded + * through the fallback function, which will delegateCall this address. + * + * This address should be the same for every strategy, never be adjusted + * and always be checked before any integration with the Strategy. + */ + address public constant tokenizedStrategyAddress = + 0xBB51273D6c746910C7C06fe718f30c936170feD0; + + /*////////////////////////////////////////////////////////////// + IMMUTABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Underlying asset the Strategy is earning yield on. + * Stored here for cheap retrievals within the strategy. + */ + ERC20 internal immutable asset; + + /** + * @dev This variable is set to address(this) during initialization of each strategy. + * + * This can be used to retrieve storage data within the strategy + * contract as if it were a linked library. + * + * i.e. uint256 totalAssets = TokenizedStrategy.totalAssets() + * + * Using address(this) will mean any calls using this variable will lead + * to a call to itself. Which will hit the fallback function and + * delegateCall that to the actual TokenizedStrategy. + */ + ITokenizedStrategy internal immutable TokenizedStrategy; + + /** + * @notice Used to initialize the strategy on deployment. + * + * This will set the `TokenizedStrategy` variable for easy + * internal view calls to the implementation. As well as + * initializing the default storage variables based on the + * parameters and using the deployer for the permissioned roles. + * + * @param _asset Address of the underlying asset. + * @param _name Name the strategy will use. + */ + constructor(address _asset, string memory _name) { + asset = ERC20(_asset); + + // Set instance of the implementation for internal use. + TokenizedStrategy = ITokenizedStrategy(address(this)); + + // Initialize the strategy's storage variables. + _delegateCall( + abi.encodeCall( + ITokenizedStrategy.initialize, + (_asset, _name, msg.sender, msg.sender, msg.sender) + ) + ); + + // Store the tokenizedStrategyAddress at the standard implementation + // address storage slot so etherscan picks up the interface. This gets + // stored on initialization and never updated. + assembly { + sstore( + // keccak256('eip1967.proxy.implementation' - 1) + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, + tokenizedStrategyAddress + ) + } + } + + /*////////////////////////////////////////////////////////////// + NEEDED TO BE OVERRIDDEN BY STRATEGIST + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Can deploy up to '_amount' of 'asset' in the yield source. + * + * This function is called at the end of a {deposit} or {mint} + * call. Meaning that unless a whitelist is implemented it will + * be entirely permissionless and thus can be sandwiched or otherwise + * manipulated. + * + * @param _amount The amount of 'asset' that the strategy can attempt + * to deposit in the yield source. + */ + function _deployFunds(uint256 _amount) internal virtual; + + /** + * @dev Should attempt to free the '_amount' of 'asset'. + * + * NOTE: The amount of 'asset' that is already loose has already + * been accounted for. + * + * This function is called during {withdraw} and {redeem} calls. + * Meaning that unless a whitelist is implemented it will be + * entirely permissionless and thus can be sandwiched or otherwise + * manipulated. + * + * Should not rely on asset.balanceOf(address(this)) calls other than + * for diff accounting purposes. + * + * Any difference between `_amount` and what is actually freed will be + * counted as a loss and passed on to the withdrawer. This means + * care should be taken in times of illiquidity. It may be better to revert + * if withdraws are simply illiquid so not to realize incorrect losses. + * + * @param _amount, The amount of 'asset' to be freed. + */ + function _freeFunds(uint256 _amount) internal virtual; + + /** + * @dev Internal function to harvest all rewards, redeploy any idle + * funds and return an accurate accounting of all funds currently + * held by the Strategy. + * + * This should do any needed harvesting, rewards selling, accrual, + * redepositing etc. to get the most accurate view of current assets. + * + * NOTE: All applicable assets including loose assets should be + * accounted for in this function. + * + * Care should be taken when relying on oracles or swap values rather + * than actual amounts as all Strategy profit/loss accounting will + * be done based on this returned value. + * + * This can still be called post a shutdown, a strategist can check + * `TokenizedStrategy.isShutdown()` to decide if funds should be + * redeployed or simply realize any profits/losses. + * + * @return _totalAssets A trusted and accurate account for the total + * amount of 'asset' the strategy currently holds including idle funds. + */ + function _harvestAndReport() + internal + virtual + returns (uint256 _totalAssets); + + /*////////////////////////////////////////////////////////////// + OPTIONAL TO OVERRIDE BY STRATEGIST + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Optional function for strategist to override that can + * be called in between reports. + * + * If '_tend' is used tendTrigger() will also need to be overridden. + * + * This call can only be called by a permissioned role so may be + * through protected relays. + * + * This can be used to harvest and compound rewards, deposit idle funds, + * perform needed position maintenance or anything else that doesn't need + * a full report for. + * + * EX: A strategy that can not deposit funds without getting + * sandwiched can use the tend when a certain threshold + * of idle to totalAssets has been reached. + * + * This will have no effect on PPS of the strategy till report() is called. + * + * @param _totalIdle The current amount of idle funds that are available to deploy. + */ + function _tend(uint256 _totalIdle) internal virtual {} + + /** + * @dev Optional trigger to override if tend() will be used by the strategy. + * This must be implemented if the strategy hopes to invoke _tend(). + * + * @return . Should return true if tend() should be called by keeper or false if not. + */ + function _tendTrigger() internal view virtual returns (bool) { + return false; + } + + /** + * @notice Returns if tend() should be called by a keeper. + * + * @return . Should return true if tend() should be called by keeper or false if not. + * @return . Calldata for the tend call. + */ + function tendTrigger() external view virtual returns (bool, bytes memory) { + return ( + // Return the status of the tend trigger. + _tendTrigger(), + // And the needed calldata either way. + abi.encodeWithSelector(ITokenizedStrategy.tend.selector) + ); + } + + /** + * @notice Gets the max amount of `asset` that an address can deposit. + * @dev Defaults to an unlimited amount for any address. But can + * be overridden by strategists. + * + * This function will be called before any deposit or mints to enforce + * any limits desired by the strategist. This can be used for either a + * traditional deposit limit or for implementing a whitelist etc. + * + * EX: + * if(isAllowed[_owner]) return super.availableDepositLimit(_owner); + * + * This does not need to take into account any conversion rates + * from shares to assets. But should know that any non max uint256 + * amounts may be converted to shares. So it is recommended to keep + * custom amounts low enough as not to cause overflow when multiplied + * by `totalSupply`. + * + * @param . The address that is depositing into the strategy. + * @return . The available amount the `_owner` can deposit in terms of `asset` + */ + function availableDepositLimit( + address /*_owner*/ + ) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** + * @notice Gets the max amount of `asset` that can be withdrawn. + * @dev Defaults to an unlimited amount for any address. But can + * be overridden by strategists. + * + * This function will be called before any withdraw or redeem to enforce + * any limits desired by the strategist. This can be used for illiquid + * or sandwichable strategies. It should never be lower than `totalIdle`. + * + * EX: + * return TokenIzedStrategy.totalIdle(); + * + * This does not need to take into account the `_owner`'s share balance + * or conversion rates from shares to assets. + * + * @param . The address that is withdrawing from the strategy. + * @return . The available amount that can be withdrawn in terms of `asset` + */ + function availableWithdrawLimit( + address /*_owner*/ + ) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** + * @dev Optional function for a strategist to override that will + * allow management to manually withdraw deployed funds from the + * yield source if a strategy is shutdown. + * + * This should attempt to free `_amount`, noting that `_amount` may + * be more than is currently deployed. + * + * NOTE: This will not realize any profits or losses. A separate + * {report} will be needed in order to record any profit/loss. If + * a report may need to be called after a shutdown it is important + * to check if the strategy is shutdown during {_harvestAndReport} + * so that it does not simply re-deploy all funds that had been freed. + * + * EX: + * if(freeAsset > 0 && !TokenizedStrategy.isShutdown()) { + * depositFunds... + * } + * + * @param _amount The amount of asset to attempt to free. + */ + function _emergencyWithdraw(uint256 _amount) internal virtual {} + + /*////////////////////////////////////////////////////////////// + TokenizedStrategy HOOKS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Can deploy up to '_amount' of 'asset' in yield source. + * @dev Callback for the TokenizedStrategy to call during a {deposit} + * or {mint} to tell the strategy it can deploy funds. + * + * Since this can only be called after a {deposit} or {mint} + * delegateCall to the TokenizedStrategy msg.sender == address(this). + * + * Unless a whitelist is implemented this will be entirely permissionless + * and thus can be sandwiched or otherwise manipulated. + * + * @param _amount The amount of 'asset' that the strategy can + * attempt to deposit in the yield source. + */ + function deployFunds(uint256 _amount) external virtual onlySelf { + _deployFunds(_amount); + } + + /** + * @notice Should attempt to free the '_amount' of 'asset'. + * @dev Callback for the TokenizedStrategy to call during a withdraw + * or redeem to free the needed funds to service the withdraw. + * + * This can only be called after a 'withdraw' or 'redeem' delegateCall + * to the TokenizedStrategy so msg.sender == address(this). + * + * @param _amount The amount of 'asset' that the strategy should attempt to free up. + */ + function freeFunds(uint256 _amount) external virtual onlySelf { + _freeFunds(_amount); + } + + /** + * @notice Returns the accurate amount of all funds currently + * held by the Strategy. + * @dev Callback for the TokenizedStrategy to call during a report to + * get an accurate accounting of assets the strategy controls. + * + * This can only be called after a report() delegateCall to the + * TokenizedStrategy so msg.sender == address(this). + * + * @return . A trusted and accurate account for the total amount + * of 'asset' the strategy currently holds including idle funds. + */ + function harvestAndReport() external virtual onlySelf returns (uint256) { + return _harvestAndReport(); + } + + /** + * @notice Will call the internal '_tend' when a keeper tends the strategy. + * @dev Callback for the TokenizedStrategy to initiate a _tend call in the strategy. + * + * This can only be called after a tend() delegateCall to the TokenizedStrategy + * so msg.sender == address(this). + * + * We name the function `tendThis` so that `tend` calls are forwarded to + * the TokenizedStrategy. + + * @param _totalIdle The amount of current idle funds that can be + * deployed during the tend + */ + function tendThis(uint256 _totalIdle) external virtual onlySelf { + _tend(_totalIdle); + } + + /** + * @notice Will call the internal '_emergencyWithdraw' function. + * @dev Callback for the TokenizedStrategy during an emergency withdraw. + * + * This can only be called after a emergencyWithdraw() delegateCall to + * the TokenizedStrategy so msg.sender == address(this). + * + * We name the function `shutdownWithdraw` so that `emergencyWithdraw` + * calls are forwarded to the TokenizedStrategy. + * + * @param _amount The amount of asset to attempt to free. + */ + function shutdownWithdraw(uint256 _amount) external virtual onlySelf { + _emergencyWithdraw(_amount); + } + + /** + * @dev Function used to delegate call the TokenizedStrategy with + * certain `_calldata` and return any return values. + * + * This is used to setup the initial storage of the strategy, and + * can be used by strategist to forward any other call to the + * TokenizedStrategy implementation. + * + * @param _calldata The abi encoded calldata to use in delegatecall. + * @return . The return value if the call was successful in bytes. + */ + function _delegateCall( + bytes memory _calldata + ) internal returns (bytes memory) { + // Delegate call the tokenized strategy with provided calldata. + (bool success, bytes memory result) = tokenizedStrategyAddress + .delegatecall(_calldata); + + // If the call reverted. Return the error. + if (!success) { + assembly { + let ptr := mload(0x40) + let size := returndatasize() + returndatacopy(ptr, 0, size) + revert(ptr, size) + } + } + + // Return the result. + return result; + } + + /** + * @dev Execute a function on the TokenizedStrategy and return any value. + * + * This fallback function will be executed when any of the standard functions + * defined in the TokenizedStrategy are called since they wont be defined in + * this contract. + * + * It will delegatecall the TokenizedStrategy implementation with the exact + * calldata and return any relevant values. + * + */ + fallback() external { + // load our target address + address _tokenizedStrategyAddress = tokenizedStrategyAddress; + // Execute external function using delegatecall and return any value. + assembly { + // Copy function selector and any arguments. + calldatacopy(0, 0, calldatasize()) + // Execute function delegatecall. + let result := delegatecall( + gas(), + _tokenizedStrategyAddress, + 0, + calldatasize(), + 0, + 0 + ) + // Get any return value + returndatacopy(0, 0, returndatasize()) + // Return any return value or error back to the caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/test/mocks/MockTokenizedStrategy.sol b/test/mocks/MockTokenizedStrategy.sol index f7f595d..612655c 100644 --- a/test/mocks/MockTokenizedStrategy.sol +++ b/test/mocks/MockTokenizedStrategy.sol @@ -1,50 +1,21 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; -import {TokenizedStrategy, ERC20} from "@tokenized-strategy/TokenizedStrategy.sol"; +import {BaseStrategy, ERC20} from "./BaseStrategy.sol"; -contract MockTokenizedStrategy is TokenizedStrategy { +contract MockTokenizedStrategy is BaseStrategy { constructor( - address _factory, address _asset, - string memory _name, - address _management, - address _keeper - ) TokenizedStrategy(_factory) { - // Cache storage pointer - StrategyData storage S = _strategyStorage(); + string memory _name + ) BaseStrategy(_asset, _name){} - // Set the strategy's underlying asset - S.asset = ERC20(_asset); - // Set the Strategy Tokens name. - S.name = _name; - // Set decimals based off the `asset`. - S.decimals = ERC20(_asset).decimals(); + function _deployFunds(uint256 _amount) internal override virtual {} - // Set last report to this block. - S.lastReport = uint96(block.timestamp); + function _freeFunds(uint256 _amount) internal override virtual {} - // Set the default management address. Can't be 0. - require(_management != address(0), "ZERO ADDRESS"); - S.management = _management; - S.performanceFeeRecipient = _management; - // Set the keeper address - S.keeper = _keeper; - } - - function availableWithdrawLimit( - address /*_owner*/ - ) public view virtual returns (uint256) { - return type(uint256).max; - } - - function deployFunds(uint256 _amount) external virtual {} - - function freeFunds(uint256 _amount) external virtual {} - - function harvestAndReport() external virtual returns (uint256) { - return _strategyStorage().asset.balanceOf(address(this)); + function _harvestAndReport() internal override virtual returns (uint256) { + return asset.balanceOf(address(this)); } } @@ -53,23 +24,19 @@ contract MockTokenized is MockTokenizedStrategy { uint256 public limit; constructor( - address _factory, address _asset, - string memory _name, - address _management, - address _keeper - ) MockTokenizedStrategy(_factory, _asset, _name, _management, _keeper) { - } + string memory _name + ) MockTokenizedStrategy(_asset, _name){} function realizeLoss(uint256 _amount) external { - _strategyStorage().asset.transfer(msg.sender, _amount); + asset.transfer(msg.sender, _amount); } function availableWithdrawLimit( address _owner ) public view virtual override returns (uint256) { if (limit != 0) { - uint256 _totalAssets = _strategyStorage().totalAssets; + uint256 _totalAssets = TokenizedStrategy.totalAssets(); return _totalAssets > limit ? _totalAssets - limit : 0; } else { return super.availableWithdrawLimit(_owner); diff --git a/test/setup.t.sol b/test/setup.t.sol new file mode 100644 index 0000000..d873dbf --- /dev/null +++ b/test/setup.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import {Setup, console} from "./utils/Setup.sol"; + +contract SetupTest is Setup { + + function setUp() public virtual override{ + super.setUp(); + } + + function test_setupOk() public { + assertNeq(address(registry), address(0)); + assertNeq(address(accountant), address(0)); + assertNeq(address(allocatorFactory), address(0)); + assertNeq(address(l1Deployer), address(0)); + assertNeq(address(l1EscrowImpl), address(0)); + assertNeq(address(l2Deployer), address(0)); + assertNeq(address(l2EscrowImpl), address(0)); + assertNeq(address(l2TokenImpl), address(0)); + assertNeq(address(l2TokenConverterImpl), address(0)); + } + + function test_newVault() public { + // Pretend to be the rollup 1 + uint32 rollupID = 1; + address rollupContract = 0x519E42c24163192Dca44CD3fBDCEBF6be9130987; + address admin = 0x242daE44F5d8fb54B198D03a94dA45B5a4413e21; + address manager = address(123); + + vm.expectRevert("!admin"); + l1Deployer.registerRollup(rollupID, manager); + + vm.prank(admin); + l1Deployer.registerRollup(rollupID, manager); + + l1Deployer.newAsset(rollupID, address(asset)); + + assert(false); + } +} \ No newline at end of file diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index 388784d..ad35459 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -5,36 +5,81 @@ import "forge-std/console.sol"; import {ExtendedTest} from "./ExtendedTest.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -//import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol/"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Roles} from "@yearn-vaults/interfaces/Roles.sol"; import {IVault} from "@yearn-vaults/interfaces/IVault.sol"; -import {IStrategy} from "@tokenized-strategy/interfaces/IStrategy.sol"; +import {IStrategy} from "../../src/interfaces/Yearn/IStrategy.sol"; import {IVaultFactory} from "@yearn-vaults/interfaces/IVaultFactory.sol"; import {Registry, RegistryFactory} from "@vault-periphery/registry/RegistryFactory.sol"; +import {DebtAllocator, DebtAllocatorFactory} from "@vault-periphery/debtAllocators/DebtAllocatorFactory.sol"; + +import {IAccountant} from "../../src/interfaces/Yearn/IAccountant.sol"; +import {IAccountantFactory} from "../../src/interfaces/Yearn/IAccountantFactory.sol"; + +import {L1Deployer} from "../../src/L1Deployer.sol"; +import {L2Deployer} from "../../src/L2Deployer.sol"; +import {L1YearnEscrow} from "../../src/L1YearnEscrow.sol"; + +import {L2Escrow} from "@zkevm-stb/L2Escrow.sol"; +import {L2Token} from "@zkevm-stb/L2Token.sol"; +import {L2TokenConverter} from "@zkevm-stb/L2TokenConverter.sol"; import {MockStrategy} from "../mocks/MockStrategy.sol"; contract Setup is ExtendedTest { + using SafeERC20 for ERC20; // Contract instances that we will use repeatedly. ERC20 public asset; IStrategy public mockStrategy; + address public zkEvmBridge = 0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe; + + address public rollupManager = 0x5132A183E9F3CB7C848b0AAC5Ae0c4f0491B7aB2; + // Vault contracts to test with. IVault public vault; // Vault Factory v3.0.2 IVaultFactory public vaultFactory = IVaultFactory(0x444045c5C13C246e117eD36437303cac8E250aB0); - // Addresses for different roles we will use repeatedly. - address public user = address(10); - address public keeper = address(4); - address public daddy = address(69); - address public management = address(1); - address public vaultManagement = address(2); - address public performanceFeeRecipient = address(3); + /// Periphery Contracts \\\ + + Registry public registry; + RegistryFactory public registryFactory = RegistryFactory(0x8648FF16ed48FAD456BF0e0e2190AeA8710BdC81); + + DebtAllocatorFactory public allocatorFactory; + + IAccountant public accountant; + IAccountantFactory public accountantFactory = IAccountantFactory(0xF728f839796a399ACc2823c1e5591F05a31c32d1); + + /// Core Contracts \\\\ + + ///// L1 Contracts \\\\\ + L1Deployer public l1Deployer; + + L1YearnEscrow public l1EscrowImpl; + + //// L2 Contracts \\\\\ + + L2Deployer public l2Deployer; + + L2Escrow public l2EscrowImpl; + L2Token public l2TokenImpl; + + L2TokenConverter public l2TokenConverterImpl; + + // Addresses for different roles we will use repeatedly. + address public czar = address(1); + address public user = address(2); + address public keeper = address(3); + address public management = address(4); + address public governator = address(69); + address public feeRecipient = address(5); + address public emergencyAdmin = address(6); + mapping(string => address) public tokenAddrs; // Integer variables that will be used repeatedly. @@ -52,25 +97,74 @@ contract Setup is ExtendedTest { function setUp() public virtual { _setTokenAddrs(); + // Deploy new Registry. + registry = Registry( + registryFactory.createNewRegistry("Test STB Registry", governator) + ); + + // Deploy new Debt Allocator Factory. + allocatorFactory = DebtAllocatorFactory(new DebtAllocatorFactory(governator)); + + // Deploy new Accountant + accountant = IAccountant(accountantFactory.newAccountant( + governator, // governance + feeRecipient, // Fee recipient + 0, // Management Fee + 1_000, // Perf Fee + 0, // Refund Ratio + 10_000, // Max Fee + 20_000, // Max Gain + 0 // Max Loss + )); + + l1EscrowImpl = new L1YearnEscrow(); + + l1Deployer = new L1Deployer( + governator, + czar, + management, + emergencyAdmin, + keeper, + address(registry), + rollupManager, + address(l1EscrowImpl) + ); + + vm.prank(governator); + registry.setEndorser(address(l1Deployer), true); + + l2TokenImpl = new L2Token(); + + l2EscrowImpl = new L2Escrow(); + + l2TokenConverterImpl = new L2TokenConverter(); + + l2Deployer = new L2Deployer(); + // Make sure everything works with USDT asset = ERC20(tokenAddrs["DAI"]); // Set decimals decimals = asset.decimals(); - vault = setupVault(); - - mockStrategy = setUpStrategy(); - // label all the used addresses for traces - vm.label(daddy, "daddy"); + vm.label(governator, "governator"); + vm.label(czar, "czar"); vm.label(keeper, "keeper"); vm.label(address(asset), "asset"); vm.label(management, "management"); vm.label(address(mockStrategy), "strategy"); - vm.label(vaultManagement, "vault management"); vm.label(address(vaultFactory), " vault factory"); - vm.label(performanceFeeRecipient, "performanceFeeRecipient"); + vm.label(feeRecipient, "feeRecipient"); + vm.label(address(registry), "Registry"); + vm.label(address(accountant), "Accountant"); + vm.label(address(allocatorFactory), "Allocator Factory"); + vm.label(address(l1Deployer), "L1 Deployer"); + vm.label(address(l1EscrowImpl), "L1 escrow IMPL"); + vm.label(address(l2Deployer), "L2 Deployer"); + vm.label(address(l2EscrowImpl), "L2 Escrow IMPL"); + vm.label(address(l2TokenImpl), "L2 Token Impl"); + vm.label(address(l2TokenConverterImpl), "L2 Convertor IMPL"); } function setupVault() public returns (IVault) {} @@ -84,7 +178,7 @@ contract Setup is ExtendedTest { // set keeper _strategy.setKeeper(keeper); // set treasury - _strategy.setPerformanceFeeRecipient(performanceFeeRecipient); + _strategy.setPerformanceFeeRecipient(feeRecipient); // set management of the strategy _strategy.setPendingManagement(management); // Accept management. @@ -116,10 +210,10 @@ contract Setup is ExtendedTest { } function addStrategyToVault(IVault _vault, IStrategy _strategy) public { - vm.prank(vaultManagement); + vm.prank(governator); _vault.add_strategy(address(_strategy)); - vm.prank(vaultManagement); + vm.prank(governator); _vault.update_max_debt_for_strategy( address(_strategy), type(uint256).max @@ -131,7 +225,7 @@ contract Setup is ExtendedTest { IStrategy _strategy, uint256 _amount ) public { - vm.prank(vaultManagement); + vm.prank(governator); _vault.update_debt(address(_strategy), _amount); } From 39829a317943e975fd5624abcb8c0e8bc74cf1ec Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Thu, 18 Apr 2024 06:19:33 -0600 Subject: [PATCH 15/16] build: l2 deployer --- src/DeployerBase.sol | 20 ++ src/L1Deployer.sol | 102 ++++--- src/L1YearnEscrow.sol | 14 +- src/L2Deployer.sol | 277 +++++++++++++++++- src/interfaces/ICREATE3Factory.sol | 2 +- .../Polygon/IPolygonRollupManager.sol | 31 -- test/setup.t.sol | 7 +- test/utils/Setup.sol | 2 +- 8 files changed, 372 insertions(+), 83 deletions(-) create mode 100644 src/DeployerBase.sol diff --git a/src/DeployerBase.sol b/src/DeployerBase.sol new file mode 100644 index 0000000..aa744db --- /dev/null +++ b/src/DeployerBase.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract DeployerBase { + ICREATE3Factory internal constant create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); + + address public immutable counterPartContract; + + address public immutable polygonZkEVMBridge; + + constructor(address _counterPartContract, address _polygonZkEVMBridge) { + counterPartContract = _counterPartContract; + polygonZkEVMBridge = _polygonZkEVMBridge; + } +} diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index 0d7bda6..c147be0 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -3,22 +3,24 @@ pragma solidity ^0.8.20; import {L1YearnEscrow} from "./L1YearnEscrow.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IPolygonZkEVMBridge} from "@zkevm-stb/interfaces/IPolygonZkEVMBridge.sol"; import {IPolygonRollupManager, IPolygonRollupContract} from "./interfaces/Polygon/IPolygonRollupManager.sol"; -import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; import {Proxy} from "@zkevm-stb/Proxy.sol"; import {RoleManager} from "./RoleManager.sol"; +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + // TODO: // 1. Deposit Limits/module // create 3 factory // External create3 Address getters -/// Governace Structure: +/// Governance Structure: // 1. GOVERNATOR Can change the Holders, Impl and addresses (Rare) 2/3 meta multisig (No Roles) // 2. CZAR/DADDY Sets strategies All Roles // 3. Management/SMS Day to Day Ops @@ -31,6 +33,8 @@ contract L1Deployer is RoleManager { address indexed manager ); + event UpdateRollupManager(uint32 indexed rollupID, address indexed manager); + event NewL1Escrow(uint32 indexed rollupID, address indexed l1Escrow); struct ChainConfig { @@ -40,36 +44,37 @@ contract L1Deployer is RoleManager { } /// @notice Only allow either governance or the position holder to call. - modifier onlyChainAdmin(uint32 _rollupID) { - _isChainAdmin(_rollupID); + modifier onlyRollupAdmin(uint32 _rollupID) { + _isRollupAdmin(_rollupID); _; } /// @notice Check if the msg sender is governance or the specified position holder. - function _isChainAdmin(uint32 _rollupID) internal view virtual { + function _isRollupAdmin(uint32 _rollupID) internal view virtual { require( msg.sender == chainConfig[_rollupID].rollupContract.admin(), "!admin" ); } - ICREATE3Factory internal create3Factory = - ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); - /*////////////////////////////////////////////////////////////// POSITION ID'S //////////////////////////////////////////////////////////////*/ bytes32 public constant ESCROW_IMPLEMENTATION = keccak256("Escrow Implementation"); + bytes32 public constant L2_DEPLOYER = keccak256("L2 Deployer"); - uint256 public immutable originalID; + ICREATE3Factory internal constant create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); - address public immutable bridgeAddress; + uint256 public immutable originalID; address public immutable rollupManager; + address public immutable polygonZkEVMBridge; + /*////////////////////////////////////////////////////////////// STORAGE //////////////////////////////////////////////////////////////*/ @@ -97,8 +102,9 @@ contract L1Deployer is RoleManager { ) { originalID = block.chainid; - bridgeAddress = IPolygonRollupManager(_rollupManager).bridgeAddress(); rollupManager = _rollupManager; + polygonZkEVMBridge = IPolygonRollupManager(_rollupManager) + .bridgeAddress(); _positions[ESCROW_IMPLEMENTATION].holder = _escrowImplementation; } @@ -130,6 +136,16 @@ contract L1Deployer is RoleManager { ); } + function updateRollupManager( + uint32 _rollupID, + address _l1Manager + ) external virtual onlyRollupAdmin(_rollupID) { + require(_l1Manager != address(0), "ZERO ADDRESS"); + chainConfig[_rollupID].manager = _l1Manager; + + emit UpdateRollupManager(_rollupID, _l1Manager); + } + /*////////////////////////////////////////////////////////////// ESCROW CREATION //////////////////////////////////////////////////////////////*/ @@ -137,29 +153,13 @@ contract L1Deployer is RoleManager { function newAsset( uint32 _rollupID, address _asset - ) external returns (address, address) { + ) external virtual returns (address _vault, address _l1Escrow) { // Verify the rollup Id is valid. require( address(chainConfig[_rollupID].rollupContract) != address(0), "rollup not registered" ); - return _newAsset(_rollupID, _asset, 0); - } - - function newAsset( - uint32 _rollupID, - address _asset, - uint256 _minimumBuffer - ) external onlyChainAdmin(_rollupID) returns (address, address) { - // Modifier passing implies a valid rollup ID. - return _newAsset(_rollupID, _asset, _minimumBuffer); - } - function _newAsset( - uint32 _rollupID, - address _asset, - uint256 _minimumBuffer - ) internal virtual returns (address _vault, address _l1Escrow) { // Verify that the vault is not already set for that chain. _l1Escrow = getEscrow(_rollupID, _asset); if (_l1Escrow != address(0)) revert AlreadyDeployed(_l1Escrow); @@ -173,13 +173,13 @@ contract L1Deployer is RoleManager { } // Deploy L1 Escrow. - _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault, _minimumBuffer); + _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault); } function newCustomAsset( uint32 _rollupID, address _asset - ) external virtual onlyChainAdmin(_rollupID) returns (address _vault) { + ) external virtual onlyRollupAdmin(_rollupID) returns (address _vault) { string memory _rollupIDString = Strings.toString(_rollupID); // Name is "{SYMBOL}-STB-{rollupID} yVault" @@ -218,7 +218,7 @@ contract L1Deployer is RoleManager { uint32 _rollupID, address _asset, address _vault - ) external virtual onlyChainAdmin(_rollupID) { + ) external virtual onlyRollupAdmin(_rollupID) { _addNewVault(_rollupID, _vault); _newCustomAsset(_rollupID, _asset, _vault); } @@ -231,7 +231,7 @@ contract L1Deployer is RoleManager { address _l1Escrow = getEscrow(_rollupID, _asset); if (_l1Escrow == address(0)) { - _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault, 0); + _l1Escrow = _deployL1Escrow(_rollupID, _asset, _vault); } _assetToVault[_asset][_rollupID] = _vault; @@ -267,8 +267,7 @@ contract L1Deployer is RoleManager { function _deployL1Escrow( uint32 _rollupID, address _asset, - address _vault, - uint256 _minimumBuffer + address _vault ) internal returns (address _l1Escrow) { ChainConfig storage _chainConfig = chainConfig[_rollupID]; @@ -278,13 +277,12 @@ contract L1Deployer is RoleManager { L1YearnEscrow.initialize.selector, _chainConfig.rollupContract.admin(), _chainConfig.manager, - bridgeAddress, + polygonZkEVMBridge, _getL2EscrowAddress(symbol), _rollupID, _asset, _getL2TokenAddress(symbol), - _vault, - _minimumBuffer + _vault ); bytes memory creationCode = abi.encodePacked( @@ -305,19 +303,25 @@ contract L1Deployer is RoleManager { // Send Message to Bridge for L2 // TODO: Will L2 Deployer be the same each chain? - IPolygonZkEVMBridge(bridgeAddress).bridgeMessage( + IPolygonZkEVMBridge(polygonZkEVMBridge).bridgeMessage( _rollupID, getPositionHolder(L2_DEPLOYER), false, - abi.encode(_asset, _l1Escrow) + abi.encode(_asset, _l1Escrow, ERC20(_asset).name(), string(symbol)) ); emit NewL1Escrow(_rollupID, _l1Escrow); } + function getL1EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL1EscrowAddress(bytes(ERC20(_asset).symbol())); + } + function _getL1EscrowAddress( bytes memory _symbol - ) internal returns (address) { + ) internal view returns (address) { return create3Factory.getDeployed( address(this), @@ -325,10 +329,16 @@ contract L1Deployer is RoleManager { ); } + function getL2EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL2EscrowAddress(bytes(ERC20(_asset).symbol())); + } + // Address will be the L2 deployer function _getL2EscrowAddress( bytes memory _symbol - ) internal returns (address) { + ) internal view returns (address) { return create3Factory.getDeployed( getPositionHolder(L2_DEPLOYER), @@ -336,9 +346,15 @@ contract L1Deployer is RoleManager { ); } + function getL2TokenAddress( + address _asset + ) external view virtual returns (address) { + return _getL2TokenAddress(bytes(ERC20(_asset).symbol())); + } + function _getL2TokenAddress( bytes memory _symbol - ) internal returns (address) { + ) internal view returns (address) { return create3Factory.getDeployed( getPositionHolder(L2_DEPLOYER), diff --git a/src/L1YearnEscrow.sol b/src/L1YearnEscrow.sol index 83bf772..8d8343e 100644 --- a/src/L1YearnEscrow.sol +++ b/src/L1YearnEscrow.sol @@ -64,7 +64,6 @@ contract L1YearnEscrow is L1Escrow { * @param _originTokenAddress Token address * @param _wrappedTokenAddress L2Token address on Polygon ZkEVM * @param _vaultAddress Address of the vault to use. - * @param _minimumBuffer Buffer if any to keep idle. */ function initialize( address _admin, @@ -74,8 +73,7 @@ contract L1YearnEscrow is L1Escrow { uint32 _counterpartNetwork, address _originTokenAddress, address _wrappedTokenAddress, - address _vaultAddress, - uint256 _minimumBuffer + address _vaultAddress ) public virtual initializer { // Initialize the default escrow. initialize( @@ -91,7 +89,6 @@ contract L1YearnEscrow is L1Escrow { VaultStorage storage $ = _getVaultStorage(); // Set the vault variables $.vaultAddress = IVault(_vaultAddress); - $.minimumBuffer = _minimumBuffer; } // **************************** @@ -177,6 +174,11 @@ contract L1YearnEscrow is L1Escrow { // * Admin * // **************************** + /** + * @dev Update the vault to deploy funds into. + * Will fully withdraw from the old vault. + * @param _vaultAddress Address of the new vault to use. + */ function updateVault( address _vaultAddress ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { @@ -195,6 +197,10 @@ contract L1YearnEscrow is L1Escrow { emit UpdateVaultAddress(_vaultAddress); } + /** + * @dev Update the minimum buffer to keep in the escrow. + * @param _minimumBuffer The new minimum buffer to enforce. + */ function updateMinimumBuffer( uint256 _minimumBuffer ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { diff --git a/src/L2Deployer.sol b/src/L2Deployer.sol index adf8d51..17a3b02 100644 --- a/src/L2Deployer.sol +++ b/src/L2Deployer.sol @@ -1,4 +1,279 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; -contract L2Deployer {} +import {Proxy} from "@zkevm-stb/Proxy.sol"; +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {L2Escrow} from "@zkevm-stb/L2Escrow.sol"; +import {L2Token} from "@zkevm-stb/L2Token.sol"; +import {L2TokenConverter} from "@zkevm-stb/L2TokenConverter.sol"; + +import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract L2Deployer { + struct TokenInfo { + address l1Token; + address l2Token; + address l1Escrow; + address l2Escrow; + address l2Convertor; + } + + uint32 internal constant ORIGIN_NETWORK_ID = 0; + + ICREATE3Factory internal constant create3Factory = + ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); + + address public immutable counterpartContract; + + address public immutable polygonZkEVMBridge; + + address public l2Admin; + + address public riskManager; + + address public escrowManager; + + address public tokenImplementation; + + address public escrowImplementation; + + address public convertorImplementation; + + // + mapping(string => TokenInfo) public tokenInfo; + + constructor( + address _l2Admin, + address _riskManager, + address _escrowManager, + address _polygonZkEVMBridge, + address _counterpartContract, + address _tokenImplementation, + address _escrowImplementation, + address _convertorImplementation + ) { + l2Admin = _l2Admin; + riskManager = _riskManager; + escrowManager = _escrowManager; + polygonZkEVMBridge = _polygonZkEVMBridge; + counterpartContract = _counterpartContract; + tokenImplementation = _tokenImplementation; + escrowImplementation = _escrowImplementation; + convertorImplementation = _convertorImplementation; + } + + /** + * @notice Function triggered by the bridge once a message is received by the other network + * @param originAddress Origin address that the message was sended + * @param originNetwork Origin network that the message was sended ( not usefull for this contract) + * @param data Abi encoded metadata + */ + function onMessageReceived( + address originAddress, + uint32 originNetwork, + bytes memory data + ) external payable { + // Can only be called by the bridge + require( + polygonZkEVMBridge == msg.sender, + "L2Deployer: Not PolygonZkEVMBridge" + ); + require( + counterpartContract == originAddress, + "L2Deployer: Not counterpart contract" + ); + require( + ORIGIN_NETWORK_ID == originNetwork, + "L2Deployer: Not counterpart network" + ); + + _onMessageReceived(data); + } + + /** + * @notice Internal function triggered when receive a message + * @param data message data containing the destination address and the token amount + */ + function _onMessageReceived(bytes memory data) internal { + // Decode message data + ( + address _originAddress, + address _l1Escrow, + string memory _name, + string memory _symbol + ) = abi.decode(data, (address, address, string, string)); + + // Get addresses + address expectedTokenAddress = _getL2TokenAddress(_symbol); + address expectedEscrowAddress = _getL2EscrowAddress(_symbol); + address expectedConvertorAddress = _getL2ConvertorAddress(_symbol); + + // Deploy Token + address _l2Token = _deployL2Token( + _symbol, + expectedEscrowAddress, + expectedConvertorAddress + ); + require(_l2Token == expectedTokenAddress, "wrong address"); + + // Deploy escrow + address _l2Escrow = _deployL2Escrow(_symbol, _originAddress, _l2Token); + require(_l2Escrow == expectedEscrowAddress, "wrong address"); + + // Deploy Convertor + address _l2Convertor = _deployL2Convertor(_symbol, _l2Token); + require(_l2Convertor == expectedConvertorAddress, "wrong address"); + + // Store Data + tokenInfo[string(_symbol)] = TokenInfo({ + l1Escrow: _l1Escrow, + l2Escrow: _l2Escrow, + l1Token: _originAddress, + l2Token: _l2Token, + l2Convertor: _l2Convertor + }); + } + + function _deployL2Token( + string memory _name, + bytes memory _symbol, + address _l2Escrow, + address _l2Convertor + ) internal virtual returns (address _tokenAddress) { + bytes memory data = abi.encodeWithSelector( + L2Token.initialize.selector, + l2Admin, + _l2Escrow, + _l2Convertor, + _name, + string(_symbol) + ); + + bytes memory creationCode = abi.encodePacked( + type(Proxy).creationCode, + abi.encode(tokenImplementation, data) + ); + + _tokenAddress = create3Factory.deploy( + keccak256(abi.encodePacked(bytes("L2Token:"), _symbol)), + creationCode + ); + } + + function _deployL2Escrow( + bytes memory _symbol, + address _originTokenAddress, + address _l2TokenAddress + ) internal virtual returns (address _escrowAddress) { + bytes memory data = abi.encodeWithSelector( + L2Escrow.initialize.selector, + l2Admin, + polygonZkEVMBridge, + counterpartContract, + ORIGIN_NETWORK_ID, + _originTokenAddress, + _l2TokenAddress + ); + + bytes memory creationCode = abi.encodePacked( + type(Proxy).creationCode, + abi.encode(escrowImplementation, data) + ); + + _escrowAddress = create3Factory.deploy( + keccak256(abi.encodePacked(bytes("L2Escrow:"), _symbol)), + creationCode + ); + } + + function _deployL2Convertor( + bytes memory _symbol, + address _l2Token + ) internal virtual returns (address _convertorAddress) { + bytes memory data = abi.encodeWithSelector( + L2TokenConverter.initialize.selector, + l2Admin, + escrowManager, + riskManager, + _l2Token + ); + + bytes memory creationCode = abi.encodePacked( + type(Proxy).creationCode, + abi.encode(convertorImplementation, data) + ); + + _convertorAddress = create3Factory.deploy( + keccak256(abi.encodePacked(bytes("L2TokenConverter:"), _symbol)), + creationCode + ); + } + + function getL1EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL1EscrowAddress(bytes(ERC20(_asset).symbol())); + } + + function _getL1EscrowAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + counterpartContract, + keccak256(abi.encodePacked(bytes("L1Escrow:"), _symbol)) + ); + } + + function getL2EscrowAddress( + address _asset + ) external view virtual returns (address) { + return _getL2EscrowAddress(bytes(ERC20(_asset).symbol())); + } + + // Address will be the L2 deployer + function _getL2EscrowAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + address(this), + keccak256(abi.encodePacked(bytes("L2Escrow:"), _symbol)) + ); + } + + function getL2TokenAddress( + address _asset + ) external view virtual returns (address) { + return _getL2TokenAddress(bytes(ERC20(_asset).symbol())); + } + + function _getL2TokenAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + address(this), + keccak256(abi.encode(bytes("L2Token:"), _symbol)) + ); + } + + function getL2ConvertorAddress( + address _asset + ) external view virtual returns (address) { + return _getL2ConvertorAddress(bytes(ERC20(_asset).symbol())); + } + + function _getL2ConvertorAddress( + bytes memory _symbol + ) internal view returns (address) { + return + create3Factory.getDeployed( + address(this), + keccak256(abi.encode(bytes("L2Convertor:"), _symbol)) + ); + } +} diff --git a/src/interfaces/ICREATE3Factory.sol b/src/interfaces/ICREATE3Factory.sol index 41eaba9..461fc5a 100644 --- a/src/interfaces/ICREATE3Factory.sol +++ b/src/interfaces/ICREATE3Factory.sol @@ -5,7 +5,7 @@ interface ICREATE3Factory { function getDeployed( address deployer, bytes32 salt - ) external returns (address); + ) external view returns (address); function deploy( bytes32 salt, bytes memory creationCode diff --git a/src/interfaces/Polygon/IPolygonRollupManager.sol b/src/interfaces/Polygon/IPolygonRollupManager.sol index 36cb06f..fda465b 100644 --- a/src/interfaces/Polygon/IPolygonRollupManager.sol +++ b/src/interfaces/Polygon/IPolygonRollupManager.sol @@ -2,37 +2,6 @@ pragma solidity >=0.8.18; interface IPolygonRollupManager { - /** - * @notice Struct which will be stored for every batch sequence - * @param accInputHash Hash chain that contains all the information to process a batch: - * Before etrog: keccak256(bytes32 oldAccInputHash, keccak256(bytes transactions), bytes32 globalExitRoot, uint64 timestamp, address seqAddress) - * Etrog: keccak256(bytes32 oldAccInputHash, keccak256(bytes transactions), bytes32 l1InfoRoot/forcedGlobalExitRoot, uint64 currentTimestamp/forcedTimestamp, address l2Coinbase, bytes32 0/forcedBlockHashL1) - * @param sequencedTimestamp Sequenced timestamp - * @param previousLastBatchSequenced Previous last batch sequenced before the current one, this is used to properly calculate the fees - */ - struct SequencedBatchData { - bytes32 accInputHash; - uint64 sequencedTimestamp; - uint64 previousLastBatchSequenced; - } - - /** - * @notice Struct to store the pending states - * Pending state will be an intermediary state, that after a timeout can be consolidated, which means that will be added - * to the state root mapping, and the global exit root will be updated - * This is a protection mechanism against soundness attacks, that will be turned off in the future - * @param timestamp Timestamp where the pending state is added to the queue - * @param lastVerifiedBatch Last batch verified batch of this pending state - * @param exitRoot Pending exit root - * @param stateRoot Pending state root - */ - struct PendingState { - uint64 timestamp; - uint64 lastVerifiedBatch; - bytes32 exitRoot; - bytes32 stateRoot; - } - /** * @notice Struct which to store the rollup data of each chain * @param rollupContract Rollup consensus contract, which manages everything diff --git a/test/setup.t.sol b/test/setup.t.sol index d873dbf..513cf9c 100644 --- a/test/setup.t.sol +++ b/test/setup.t.sol @@ -9,6 +9,7 @@ contract SetupTest is Setup { super.setUp(); } + function test_setupOk() public { assertNeq(address(registry), address(0)); assertNeq(address(accountant), address(0)); @@ -19,6 +20,10 @@ contract SetupTest is Setup { assertNeq(address(l2EscrowImpl), address(0)); assertNeq(address(l2TokenImpl), address(0)); assertNeq(address(l2TokenConverterImpl), address(0)); + bytes memory symbol = bytes(asset.symbol()); + string memory made = string.concat(string(symbol), ".e"); + console.log("Symbol ", made); + assert(false); } function test_newVault() public { @@ -35,7 +40,5 @@ contract SetupTest is Setup { l1Deployer.registerRollup(rollupID, manager); l1Deployer.newAsset(rollupID, address(asset)); - - assert(false); } } \ No newline at end of file diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index ad35459..f247bf6 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -139,7 +139,7 @@ contract Setup is ExtendedTest { l2TokenConverterImpl = new L2TokenConverter(); - l2Deployer = new L2Deployer(); + //l2Deployer = new L2Deployer(); // Make sure everything works with USDT asset = ERC20(tokenAddrs["DAI"]); From e5ecab4a4c492335233435795b1ddadef800b813 Mon Sep 17 00:00:00 2001 From: Schlagonia Date: Thu, 18 Apr 2024 12:27:03 -0600 Subject: [PATCH 16/16] build: deploys --- src/L1Deployer.sol | 6 +++--- src/L1YearnEscrow.sol | 2 +- src/L2Deployer.sol | 28 +++++++++++++++++++--------- test/setup.t.sol | 21 ++++++++++++++++----- test/utils/Setup.sol | 16 ++++++++++++++-- 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/L1Deployer.sol b/src/L1Deployer.sol index c147be0..7220522 100644 --- a/src/L1Deployer.sol +++ b/src/L1Deployer.sol @@ -306,8 +306,8 @@ contract L1Deployer is RoleManager { IPolygonZkEVMBridge(polygonZkEVMBridge).bridgeMessage( _rollupID, getPositionHolder(L2_DEPLOYER), - false, - abi.encode(_asset, _l1Escrow, ERC20(_asset).name(), string(symbol)) + true, + abi.encode(_asset, _l1Escrow, ERC20(_asset).name(), symbol) ); emit NewL1Escrow(_rollupID, _l1Escrow); @@ -358,7 +358,7 @@ contract L1Deployer is RoleManager { return create3Factory.getDeployed( getPositionHolder(L2_DEPLOYER), - keccak256(abi.encode(bytes("L2Token:"), _symbol)) + keccak256(abi.encodePacked(bytes("L2Token:"), _symbol)) ); } diff --git a/src/L1YearnEscrow.sol b/src/L1YearnEscrow.sol index 8d8343e..1fa28a3 100644 --- a/src/L1YearnEscrow.sol +++ b/src/L1YearnEscrow.sol @@ -137,7 +137,7 @@ contract L1YearnEscrow is L1Escrow { address(this) ); if (underlyingBalance != 0) { - if (underlyingBalance > amount) { + if (underlyingBalance >= amount) { super._transferTokens(destinationAddress, amount); return; } else { diff --git a/src/L2Deployer.sol b/src/L2Deployer.sol index 17a3b02..5b9dd32 100644 --- a/src/L2Deployer.sol +++ b/src/L2Deployer.sol @@ -13,6 +13,13 @@ import {ICREATE3Factory} from "./interfaces/ICREATE3Factory.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract L2Deployer { + event NewToken( + address indexed l1Token, + address indexed l2Token, + address indexed l2Escrow, + address l2Convertor + ); + struct TokenInfo { address l1Token; address l2Token; @@ -26,10 +33,10 @@ contract L2Deployer { ICREATE3Factory internal constant create3Factory = ICREATE3Factory(0x93FEC2C00BfE902F733B57c5a6CeeD7CD1384AE1); - address public immutable counterpartContract; - address public immutable polygonZkEVMBridge; + address public immutable counterpartContract; + address public l2Admin; address public riskManager; @@ -100,11 +107,11 @@ contract L2Deployer { function _onMessageReceived(bytes memory data) internal { // Decode message data ( - address _originAddress, + address _l1Token, address _l1Escrow, string memory _name, - string memory _symbol - ) = abi.decode(data, (address, address, string, string)); + bytes memory _symbol + ) = abi.decode(data, (address, address, string, bytes)); // Get addresses address expectedTokenAddress = _getL2TokenAddress(_symbol); @@ -113,6 +120,7 @@ contract L2Deployer { // Deploy Token address _l2Token = _deployL2Token( + _name, _symbol, expectedEscrowAddress, expectedConvertorAddress @@ -120,7 +128,7 @@ contract L2Deployer { require(_l2Token == expectedTokenAddress, "wrong address"); // Deploy escrow - address _l2Escrow = _deployL2Escrow(_symbol, _originAddress, _l2Token); + address _l2Escrow = _deployL2Escrow(_symbol, _l1Token, _l2Token); require(_l2Escrow == expectedEscrowAddress, "wrong address"); // Deploy Convertor @@ -131,10 +139,12 @@ contract L2Deployer { tokenInfo[string(_symbol)] = TokenInfo({ l1Escrow: _l1Escrow, l2Escrow: _l2Escrow, - l1Token: _originAddress, + l1Token: _l1Token, l2Token: _l2Token, l2Convertor: _l2Convertor }); + + emit NewToken(_l1Token, _l2Token, _l2Escrow, _l2Convertor); } function _deployL2Token( @@ -257,7 +267,7 @@ contract L2Deployer { return create3Factory.getDeployed( address(this), - keccak256(abi.encode(bytes("L2Token:"), _symbol)) + keccak256(abi.encodePacked(bytes("L2Token:"), _symbol)) ); } @@ -273,7 +283,7 @@ contract L2Deployer { return create3Factory.getDeployed( address(this), - keccak256(abi.encode(bytes("L2Convertor:"), _symbol)) + keccak256(abi.encodePacked(bytes("L2TokenConverter:"), _symbol)) ); } } diff --git a/test/setup.t.sol b/test/setup.t.sol index 513cf9c..cc59df7 100644 --- a/test/setup.t.sol +++ b/test/setup.t.sol @@ -20,16 +20,11 @@ contract SetupTest is Setup { assertNeq(address(l2EscrowImpl), address(0)); assertNeq(address(l2TokenImpl), address(0)); assertNeq(address(l2TokenConverterImpl), address(0)); - bytes memory symbol = bytes(asset.symbol()); - string memory made = string.concat(string(symbol), ".e"); - console.log("Symbol ", made); - assert(false); } function test_newVault() public { // Pretend to be the rollup 1 uint32 rollupID = 1; - address rollupContract = 0x519E42c24163192Dca44CD3fBDCEBF6be9130987; address admin = 0x242daE44F5d8fb54B198D03a94dA45B5a4413e21; address manager = address(123); @@ -41,4 +36,20 @@ contract SetupTest is Setup { l1Deployer.newAsset(rollupID, address(asset)); } + + function test_newToken() public { + uint32 rollupID = 1; + address admin = 0x242daE44F5d8fb54B198D03a94dA45B5a4413e21; + address manager = address(123); + + vm.prank(admin); + l1Deployer.registerRollup(rollupID, manager); + + address _l1Escrow; + ( , _l1Escrow) = l1Deployer.newAsset(rollupID, address(asset)); + bytes memory data = abi.encode(address(asset), _l1Escrow, asset.name(), bytes(asset.symbol())); + + vm.prank(polygonZkEVMBridge); + l2Deployer.onMessageReceived(address(l1Deployer), 0, data); + } } \ No newline at end of file diff --git a/test/utils/Setup.sol b/test/utils/Setup.sol index f247bf6..6f22d25 100644 --- a/test/utils/Setup.sol +++ b/test/utils/Setup.sol @@ -35,7 +35,7 @@ contract Setup is ExtendedTest { ERC20 public asset; IStrategy public mockStrategy; - address public zkEvmBridge = 0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe; + address public polygonZkEVMBridge = 0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe; address public rollupManager = 0x5132A183E9F3CB7C848b0AAC5Ae0c4f0491B7aB2; @@ -75,10 +75,13 @@ contract Setup is ExtendedTest { address public czar = address(1); address public user = address(2); address public keeper = address(3); + address public l2Admin = address(70); address public management = address(4); address public governator = address(69); address public feeRecipient = address(5); address public emergencyAdmin = address(6); + address public l2RiskManager = address(48); + address public l2EscrowManager = address(67); mapping(string => address) public tokenAddrs; @@ -139,7 +142,16 @@ contract Setup is ExtendedTest { l2TokenConverterImpl = new L2TokenConverter(); - //l2Deployer = new L2Deployer(); + l2Deployer = new L2Deployer( + l2Admin, + l2RiskManager, + l2EscrowManager, + polygonZkEVMBridge, + address(l1Deployer), + address(l2TokenImpl), + address(l2EscrowImpl), + address(l2TokenConverterImpl) + ); // Make sure everything works with USDT asset = ERC20(tokenAddrs["DAI"]);